PHP7字符串_zend_string 结构体定义如下:
//Zend/zend_types.h
typedef struct _zend_refcounted_h {
uint32_t refcount; /* 4字节,存储引用计数 */
union {
struct {
ZEND_ENDIAN_LOHI_3(
zend_uchar type, /* 字符串的类型,等同于u1.v.type */
zend_uchar flags, /* 类型标记 */
uint16_t gc_info) /* 垃圾回收颜色标识字段 */
} v;
uint32_t type_info;
} u;
} zend_refcounted_h;
struct _zend_string {
zend_refcounted_h gc; /* 8字节,存储字符串类别以及引用计数 */
zend_ulong h; /* 8字节,字符串对应的哈希值 */
size_t len; /* 8字节,字符串的长度 */
char val[1]; /* 1字节(柔性数组),字符串的值的存储位置 */
};
_zend_refcounted_h 结构体的u.v.type的取值等同于第四讲中的u1.v.type。u.type_info字段也在第四讲中说明过,不在累述。《(四)php7zval源码解读》
_zend_refcounted_h 结构体的u.v.flags取值如下(下面实例中当做对照表参考具体的flags取值):
/* string flags (zval.value->gc.u.flags) */
#define IS_STR_PERSISTENT (1<<0) /* allocated using malloc */
#define IS_STR_INTERNED (1<<1) /* interned string */
#define IS_STR_PERMANENT (1<<2) /* relives request boundary */
#define IS_STR_CONSTANT (1<<3) /* constant index */
#define IS_STR_CONSTANT_UNQUALIFIED (1<<4) /* the same as IS_CONSTANT_UNQUALIFIED */
通过zval.value.str可以取到该字符串结构体的首地址,其中zval表示zval结构体变量,如下图所示:
不是每次将字符串变量a赋值给变量b的时候,refcount都会加+,下面分情况来讨论。
//string2.php
$c = "hello world";
echo $c;
$d = $c;
echo $d;
?>
echo $c的结果:
echo $d的结果:
分析:对于常量字符串,将c赋值给d的时候,两个zval变量的str指向的是同一个内存空间,并且refcount并没有+1。
$a = "time:".time();
echo $a;
$b = $a;
echo $b;
echo $a的结果:
echo $b的结果:
分析:明显看到,对于临时字符串,两个zval变量中的str指向同一个内存空间,refcount的值会+1。
对于整型和浮点型,它的大小就只有16字节,当需要进行遍历赋值的时候($ a=1; $ b=$ a;)会直接给b遍历重新申请一份空间,存储的变量值和相同。但是string可以是非常大的字符串,为了节省空间,把字符串a赋值给变量b的时候,a变量和b变量指向的其实是同一个字符串,同时,gc中的refcount+1,表示字符串多了一个引用它的变量,而当修改b的时候,a还是指向原来的字符串,b的修改操作会触发字符串copy一份给b,这样b修改了,不会影响a。
//string3.php
$a="time:".time();
echo $a;
$b=$a;
echo $b;
$b="hello world";
echo $a;
echo $b;
//echo $a
//echo $b
//当修改b的值,在echo $a,发现a的引用计数只有它自身
//此时b的值为“hello world”,echo $b,发现b的zval.str的地址值已经发生了变化,并且refcount的值为0
不仅是把字符串变量a赋值给字符串变量b时,变量a和b指向的str指向的是同一个字符串地址,当定义变量a和变量b是相同的字符串时,两个变量的zval.str也是指向同一个字符串地址。下面来验证下:
$a = "hello";
$b = "hello";
echo $a;
echo $b;
/* {{{ php_explode
*/
PHPAPI void php_explode(const zend_string *delim, zend_string *str, zval *return_value, zend_long limit)
{
//p1指向原字符串的首地址
char *p1 = ZSTR_VAL(str);
//endp指向原字符串的未地址
char *endp = ZSTR_VAL(str) + ZSTR_LEN(str);
//调用php_memnstr()查找delim首次出现的首地址
char *p2 = (char *) php_memnstr(ZSTR_VAL(str), ZSTR_VAL(delim), ZSTR_LEN(delim), endp);
zval tmp;
if (p2 == NULL) {//如果delim未出现,则直接把str写入返回数组并返回
ZVAL_STR_COPY(&tmp, str);
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp);
} else {
do {
//把str p1到p2之间的字符串写入tmp
ZVAL_STRINGL(&tmp, p1, p2 - p1);
//把tmp写入需要返回的数组中
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp);
//把p1偏移至str下一次需要查找的首地址
p1 = p2 + ZSTR_LEN(delim);
////调用php_memnstr()查找delim下一次出现的首地址
p2 = (char *) php_memnstr(p1, ZSTR_VAL(delim), ZSTR_LEN(delim), endp);
} while (p2 != NULL && --limit > 1); //当p2偏移至str结束或者达到limit限制,退出循环
//如果str还有剩余部分,则将剩余部分写入tmp,并将tmp写入返回数组
if (p1 <= endp) {
ZVAL_STRINGL(&tmp, p1, endp - p1);
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp);
}
}
}