(五)PHP7 字符串源码解读

_zend_string结构体

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结构体变量,如下图所示:
(五)PHP7 字符串源码解读_第1张图片

引用计数—refcount

不是每次将字符串变量a赋值给变量b的时候,refcount都会加+,下面分情况来讨论。

常量字符串

//string2.php


$c = "hello world";
echo $c;
$d = $c;
echo $d;
?>

echo $c的结果:
(五)PHP7 字符串源码解读_第2张图片
echo $d的结果:
(五)PHP7 字符串源码解读_第3张图片
分析:对于常量字符串,将c赋值给d的时候,两个zval变量的str指向的是同一个内存空间,并且refcount并没有+1。

临时字符串


$a = "time:".time();
echo $a;
$b = $a;

echo $b;

echo $a的结果:
(五)PHP7 字符串源码解读_第4张图片
echo $b的结果:
(五)PHP7 字符串源码解读_第5张图片
分析:明显看到,对于临时字符串,两个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
(五)PHP7 字符串源码解读_第6张图片
//echo $b
(五)PHP7 字符串源码解读_第7张图片
//当修改b的值,在echo $a,发现a的引用计数只有它自身
(五)PHP7 字符串源码解读_第8张图片
//此时b的值为“hello world”,echo $b,发现b的zval.str的地址值已经发生了变化,并且refcount的值为0
(五)PHP7 字符串源码解读_第9张图片

另一个示例

不仅是把字符串变量a赋值给字符串变量b时,变量a和b指向的str指向的是同一个字符串地址,当定义变量a和变量b是相同的字符串时,两个变量的zval.str也是指向同一个字符串地址。下面来验证下:


$a = "hello";
$b = "hello";
echo $a;
echo $b;       

(五)PHP7 字符串源码解读_第10张图片

explode()把字符串分割成数组的源码实现

/* {{{ 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);
		}
	}
}

你可能感兴趣的:(#,PHP源码学习)