Zend_string与写时复制
字符串的结构
struct _zend_string {
zend_refcounted_h gc; /* 垃圾回收 */
zend_ulong h; /*空间换时间 hash值*/ /* hash value */
size_t len; /*长度 和下面的val可以直接表示字符串*/
char val[1];
};
/*c语言字符串用 来表示 属于非二进制安全 而php中的字符串时二进制安全的 用len和val可以直接表示字符串*/
zend_refcounted_h对应的结构体
typedef struct _zend_refcounted_h {
uint32_t refcount; /* 引用基数 */ /* reference counter 32-bit */
union {
struct {
ZEND_ENDIAN_LOHI_3(
zend_uchar type,
zend_uchar flags, /* used for strings & objects */
uint16_t gc_info) /* keeps GC root number (or 0) and color */
} v;
uint32_t type_info;
} u;
} zend_refcounted_h;
下面我们来了解一下具体每个成员的作用:
- gc:就是_zend_refcounted_h结构体,主要作用是引用计数以及标记变量的类别。
- h:字符串的哈希值,在字符串被用来当数组的key时才初始化,这样如果同一个字符串被多次用来做key,就不会重复计算了。
- val:这里的char[1]并不意味着只存储1位,char[1]被称为柔性数组
字符串的二进制安全
学习过C语言的应该知道,字符串中除了最后一个字符外不允许含有 ,否则会被认为是字符串的结束字符,这就导致了C语言的字符串有很多的限制,比如不存储图片、文件等二进制数据。但是PHP就没有这样的限制,它的字符串可以存储二进制数据,并不会出现任何报错,而PHP的这种能力就叫做字符串的二进制安全 c语言代码片段
main() {
char a[] = "aaa b"; /* 含有 的字符串 */
printf("%dn", strlen(a)); /* 长度为3, 后的b被忽略 */
}
php代码片段
<?php
$a = "aaa b";
echo strlen($a); //输出5
?>
但是PHP不是C语言写的吗?为什么PHP不会报错?我们再来回顾一下zend_string结构体,还记得成员变量len吗?它是实现二进制安全的关键,我们不需要像C一样通过 来判定字符串是否被读取完成,而是通过长度len来判断,这样就保证了字符串的二进制安全。
写时复制: 当b = a 这种操作的时候 a和b是指向同一个zend_val的,内存只有一份,节约了空间,用gc中的refcount+1来标记 我们看代码如下
<?php
$c = "hello world";
echo $c;
$a = time()." string";
echo $a;
//写时复制
$b = $a;
echo $a;
echo $b;
$b = "hello";
echo $a;
echo $b;
在c = "hello world"; echo
(gdb) p *z
$1 = {value = {lval = 140737314684512, dval = 6.9533472273566172e-310, counted = 0x7ffff5a5fe60, str = 0x7ffff5a5fe60, arr = 0x7ffff5a5fe60, obj = 0x7ffff5a5fe60,
res = 0x7ffff5a5fe60, ref = 0x7ffff5a5fe60, ast = 0x7ffff5a5fe60, zv = 0x7ffff5a5fe60, ptr = 0x7ffff5a5fe60, ce = 0x7ffff5a5fe60, func = 0x7ffff5a5fe60, ww = {w1 = 4121296480,
w2 = 32767}}, u1 = {v = {type = 6 ' 06', type_flags = 0 ' 00', const_flags = 0 ' 00', reserved = 0 ' 00'}, type_info = 6}, u2 = {next = 0, cache_slot = 0, lineno = 0,
num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, extra = 0}}
(gdb) p *$1.value.str
$2 = {gc = {refcount = 0, u = {v = {type = 6 ' 06', flags = 2 ' 02', gc_info = 0}, type_info = 518}}, h = 13876786532495509697, len = 11, val = "h"}
(gdb) p *$1.value.str.val@11
$3 = "hello world"
refcount=0而且flags=2表示一个常量
在a = time()." string"; echo
(gdb) p *z
$4 = {value = {lval = 140737314684672, dval = 6.9533472273645223e-310, counted = 0x7ffff5a5ff00, str = 0x7ffff5a5ff00, arr = 0x7ffff5a5ff00, obj = 0x7ffff5a5ff00,
res = 0x7ffff5a5ff00, ref = 0x7ffff5a5ff00, ast = 0x7ffff5a5ff00, zv = 0x7ffff5a5ff00, ptr = 0x7ffff5a5ff00, ce = 0x7ffff5a5ff00, func = 0x7ffff5a5ff00, ww = {w1 = 4121296640,
w2 = 32767}}, u1 = {v = {type = 6 ' 06', type_flags = 20 ' 24', const_flags = 0 ' 00', reserved = 0 ' 00'}, type_info = 5126}, u2 = {next = 0, cache_slot = 0,
lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, extra = 0}}
(gdb) p $4.value.str
$5 = (zend_string *) 0x7ffff5a5ff00
(gdb) p *$4.value.str
$6 = {gc = {refcount = 1, u = {v = {type = 6 ' 06', flags = 0 ' 00', gc_info = 0}, type_info = 6}}, h = 0, len = 17, val = "1"}
(gdb) p *$4.value.str.val@17
$7 = "1580219624 string"
(gdb)
refcount=1而且flags=0表示是一个变量,变量的地址是 (zend_string *) 0x7ffff5a5ff00 接下来 b = a; echo
(gdb) p *z
$8 = {value = {lval = 140737314684672, dval = 6.9533472273645223e-310, counted = 0x7ffff5a5ff00, str = 0x7ffff5a5ff00, arr = 0x7ffff5a5ff00, obj = 0x7ffff5a5ff00,
res = 0x7ffff5a5ff00, ref = 0x7ffff5a5ff00, ast = 0x7ffff5a5ff00, zv = 0x7ffff5a5ff00, ptr = 0x7ffff5a5ff00, ce = 0x7ffff5a5ff00, func = 0x7ffff5a5ff00, ww = {w1 = 4121296640,
w2 = 32767}}, u1 = {v = {type = 6 ' 06', type_flags = 20 ' 24', const_flags = 0 ' 00', reserved = 0 ' 00'}, type_info = 5126}, u2 = {next = 0, cache_slot = 0,
lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, extra = 0}}
(gdb) p $8.value.str
$9 = (zend_string *) 0x7ffff5a5ff00
(gdb) p *$8.value.str
$10 = {gc = {refcount = 2, u = {v = {type = 6 ' 06', flags = 0 ' 00', gc_info = 0}, type_info = 6}}, h = 0, len = 17, val = "1"}
(gdb) p *$8.value.str.val@17
$11 = "1580219624 string"
(gdb) c
Continuing.
1580219624 string
Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER () at /download/php-7.1.9/Zend/zend_vm_execute.h:34699
34699 SAVE_OPLINE();
(gdb) n
34700 z = _get_zval_ptr_cv_undef(execute_data, opline->op1.var);
(gdb) n
34702 if (Z_TYPE_P(z) == IS_STRING) {
(gdb) p *z
$12 = {value = {lval = 140737314684672, dval = 6.9533472273645223e-310, counted = 0x7ffff5a5ff00, str = 0x7ffff5a5ff00, arr = 0x7ffff5a5ff00, obj = 0x7ffff5a5ff00,
res = 0x7ffff5a5ff00, ref = 0x7ffff5a5ff00, ast = 0x7ffff5a5ff00, zv = 0x7ffff5a5ff00, ptr = 0x7ffff5a5ff00, ce = 0x7ffff5a5ff00, func = 0x7ffff5a5ff00, ww = {w1 = 4121296640,
w2 = 32767}}, u1 = {v = {type = 6 ' 06', type_flags = 20 ' 24', const_flags = 0 ' 00', reserved = 0 ' 00'}, type_info = 5126}, u2 = {next = 0, cache_slot = 0,
lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, extra = 0}}
(gdb) p $12.value.str
$13 = (zend_string *) 0x7ffff5a5ff00
(gdb) p *$12.value.str
$14 = {gc = {refcount = 2, u = {v = {type = 6 ' 06', flags = 0 ' 00', gc_info = 0}, type_info = 6}}, h = 0, len = 17, val = "1"}
(gdb) p *$12.value.str.val@17
$15 = "1580219624 string"
我们可以看到 两次输出 refcount=2,flags=0,并且a和b的地址都是 (zend_string *) 0x7ffff5a5ff00 说明a和b共用了一块内存,只是用refcount标记了一下而已 接下来
$b = "hello"; echo $a; echo $b;
(gdb) p *z
$2 = {value = {lval = 140737314684672, dval = 6.9533472273645223e-310, counted = 0x7ffff5a5ff00, str = 0x7ffff5a5ff00, arr = 0x7ffff5a5ff00, obj = 0x7ffff5a5ff00,
res = 0x7ffff5a5ff00, ref = 0x7ffff5a5ff00, ast = 0x7ffff5a5ff00, zv = 0x7ffff5a5ff00, ptr = 0x7ffff5a5ff00, ce = 0x7ffff5a5ff00, func = 0x7ffff5a5ff00, ww = {w1 = 4121296640,
w2 = 32767}}, u1 = {v = {type = 6 ' 06', type_flags = 20 ' 24', const_flags = 0 ' 00', reserved = 0 ' 00'}, type_info = 5126}, u2 = {next = 0, cache_slot = 0,
lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, extra = 0}}
(gdb) p *$2.value.str
$3 = {gc = {refcount = 1, u = {v = {type = 6 ' 06', flags = 0 ' 00', gc_info = 0}, type_info = 6}}, h = 0, len = 17, val = "1"}
(gdb) p *$2.value.str.val@17
$4 = "1580220167 string"
输出$a的时候 refcount已经减为1 地址为 0x7ffff5a5ff00 但是在输出b的时候
(gdb) p *z
$5 = {value = {lval = 140737314302720, dval = 6.9533472084935861e-310, counted = 0x7ffff5a02b00, str = 0x7ffff5a02b00, arr = 0x7ffff5a02b00, obj = 0x7ffff5a02b00,
res = 0x7ffff5a02b00, ref = 0x7ffff5a02b00, ast = 0x7ffff5a02b00, zv = 0x7ffff5a02b00, ptr = 0x7ffff5a02b00, ce = 0x7ffff5a02b00, func = 0x7ffff5a02b00, ww = {w1 = 4120914688,
w2 = 32767}}, u1 = {v = {type = 6 ' 06', type_flags = 0 ' 00', const_flags = 0 ' 00', reserved = 0 ' 00'}, type_info = 6}, u2 = {next = 0, cache_slot = 0, lineno = 0,
num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, extra = 0}}
(gdb) p *$5.value.str
$6 = {gc = {refcount = 0, u = {v = {type = 6 ' 06', flags = 2 ' 02', gc_info = 0}, type_info = 518}}, h = 9223372247569412249, len = 5, val = "h"}
(gdb) p *$5.value.str.val@5
$7 = "hello"
(gdb)
refcount=0,而且flags=2,地址也新开辟了一块,为0x7ffff5a02b00
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(39)-在线人数统计探讨
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(37)-文章发布系统④-百万级数据和千万级数据简单测试
- .Net 转战 Android 4.4 日常笔记(10)--PullToRefresh下拉刷新使用
- .Net 转战 Android 4.4 日常笔记(10)--ADT集成环境更新SDK
- Windows Server 2008R2 配置网络负载平衡(NLB)
- .Net 转战 Android 4.4 日常笔记(9)--常用组件的使用方法[附源码]
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(38)-Easyui-accordion+tree漂亮的菜单导航
- .Net 转战 Android 4.4 日常笔记(8)--常见事件响应及实现方式
- silverlight于javascript通信
- 微信上线小游戏:对流量基础入口应用商店革命
- Appium Desktop 使用
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(36)-文章发布系统③-kindeditor使用
- Windows Server 2008 R2 下配置证书服务器和HTTPS方式访问网站
- .Net 转战 Android 4.4 日常笔记(7)--apk的打包与反编译
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- docker swarm的常用操作
- 组件库源码中这些写法你掌握了吗?
- spark-2.4.0-hadoop2.7-安装部署 4.1. Spark安装4.2. 环境变量修改4.3. 配置修改4.4. 分发到其他机器4.5. 启动spark
- spark-2.4.0-hadoop2.7-高可用(HA)安装部署 5.1. Spark安装5.2. 环境变量修改5.3. 配置修改5.4. 分发到其他机器5.5.
- spark-2.4.0-hadoop2.7-简单操作 2.1. 相关截图
- Navicat Premium 12.0.24安装与激活(亲测已成功激活) 2.1. 下载激活文件2.2. 激活步骤准备工作2.3. 激活Navicat
- VMware实现iptables NAT及端口映射
- Saltstack_使用指南01_部署
- Saltstack_使用指南02_远程执行-验证 2.1. Master与哪些minion正常通信2.2. 查看master与指定minion通信是否正常
- Saltstack_使用指南03_配置管理
- Saltstack_使用指南04_数据系统-Grains 4.1. grains条目项信息4.2. grains全部信息4.3. 查询grains指定信息5.1. m
- 揭开spring初始化方法的神秘面纱
- Saltstack_使用指南05_数据系统-Pillar 4.1. 修改配置文件并重启服务4.2. 显示pillar信息6.1. pillar的sls文件编写6.2.
- Python Docker 查看私有仓库镜像【转】
- CentOS7 Docker私有仓库搭建及删除镜像 【转】