跟厂长学PHP7内核(八):深入理解字符串的实现

 在前面大致预览了常用变量的结构之后,我们今天来仔细的剖析一下字符串的具体实现。

一、字符串的结构

struct _zend_string {     zend_refcounted_h gc;       /* 字符串类别及引用计数 */     zend_ulong        h;        /* 字符串的哈希值 */     size_t            len;      /* 字符串的长度 */     char              val[1];   /* 柔性数组,字符串存储位置 */ };

zend_refcounted_h对应的结构体:

typedef struct _zend_refcounted_h {     uint32_t         refcount;          /* 引用计数 */     union {         struct {             ZEND_ENDIAN_LOHI_3(                 zend_uchar    type,                      zend_uchar    flags,    /* 字符串的类型 */                 uint16_t      gc_info   /* 垃圾回收信息 */             )         } v;         uint32_t type_info;     } u; } zend_refcounted_h;

image

下面我们来了解一下具体每个成员的作用:

  • gc:就是_zend_refcounted_h结构体,主要作用是引用计数以及标记变量的类别。
  • h:字符串的哈希值,在字符串被用来当数组的key时才初始化,这样如果同一个字符串被多次用来做key,就不会重复计算了。
  • val:这里的char[1]并不意味着只存储1位,char[1]被称为柔性数组,下面来了解一下PHP在字符串内存分配时做了什么。
static zend_always_inline zend_string *zend_string_alloc(size_t len, int persistent) {     zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent);     ...... }

宏替换后:

static zend_always_inline zend_string *zend_string_alloc(size_t len, int persistent) {     zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(XtOffsetOf(zend_string, val) + len + 1), persistent);     ...... }

示例中的代码XtOffsetOf(zend_string, val)表示计算出zend_string结构体的大小,而len就是要分配字符串的长度,最后的+1是留给结束字符\0的。也就是说,分配内存时不仅仅分配结构体大小的内存,还要顾及到长度不可控的val,这样不仅柔性的分配了内存,还使它与其他成员存储在同一块连续的空间中,在分配、释放内存时可以把struct统一处理。

  • len:字符串的长度,避免重复计算浪费时间,典型的空间换时间做法。

二、字符串的二进制安全

学习过C语言的应该知道,字符串中除了最后一个字符外不允许含有\0,否则会被认为是字符串的结束字符,这就导致了C语言的字符串有很多的限制,比如不存储图片、文件等二进制数据。但是PHP就没有这样的限制,它的字符串可以存储二进制数据,并不会出现任何报错,而PHP的这种能力就叫做字符串的二进制安全。

C语言代码如下:

main() {     char a[] = "aaa\0b";    /* 含有\0的字符串 */     printf("%d\n", strlen(a));  /* 长度为3,\0后的b被忽略 */ }

PHP代码:

<?php     $a = "aaa\0b";     echo strlen($a);    //输出5 ?>

但是PHP不是C语言写的吗?为什么PHP不会报错?我们再来回顾一

50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信