丁小雨2月30号见,徐州张引,夕阳红旅行社
在前面大致预览了常用变量的结构之后,我们今天来仔细的剖析一下字符串的具体实现。
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;
下面我们来了解一下具体每个成员的作用:
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统一处理。
学习过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不会报错?我们再来回顾一下zend_string结构体,还记得成员变量len吗?它是实现二进制安全的关键,我们不需要像c一样通过\0
来判定字符串是否被读取完成,而是通过长度len来判断,这样就保证了字符串的二进制安全。
在了解了zend_string结构之后,我们来了解一下用来操作zend_string的函数集合。
函数 | 作用 |
---|---|
zend_interned_strings_init | 初始化内部字符串存储哈希表,并把php的关键字等字符串信息写进去 |
zend_new_interned_string | 把一个zend_string写入cg(interned_strings)哈希表中 |
zend_interned_strings_snapshot | 将cg(interned_strings)哈希表中的字符串标记为永久字符串,这里标记的只有php关键字、内部函数名、内部方法名等 |
zend_interned_strings_restore | 销毁cg(interned_strings)哈希表中类型为非永久字符串的值,在php_request_shutdown阶段释放 |
zend_interned_strings_dtor | 销毁整个cg(interned_strings)哈希表,在php_module_shutdown阶段释放 |
zend_string_hash_val | 得到字符串的哈希值,没有则实时计算 |
zend_string_forget_hash_val | 将字符串的哈希值置为0 |
zend_string_refcount | 读取字符串的引用计数 |
zend_string_addref | 引用计数+1 |
zend_string_delref | 引用计数-1 |
zend_string_alloc | 分配内存及初始化字符串的值 |
zend_string_init | 初始化字符串并在最后追加\0 |
zend_string_cop | 使用引用计数方式复制字符串 |
zend_string_dup | 直接复制一个字符串 |
zend_string_extend | 扩容到len,保留原来的值 |
zend_string_truncate | 截断到len,保留开头到len的值 |
zend_string_free | 释放字符串内存 |
zend_string_release | gc引用递减,直到为0时释放内存 |
zend_string_equals | 普通判等 |
zend_string_equals_ci | 基于二进制安全,两个zend_string类型字符串判等 |
zend_string_equals_literal_ci | 基于二进制安全,zend_string类型和char*字符串判等 |
zend_inline_hash_func | 计算字符串的哈希值 |
zend_intern_known_strings | 往zend_intern_known_strings全局数组写入str |
下面挑几个函数来介绍一下。
zend_string_init函数主要负责把一个普通的字符串转化为zend_string结构体。
static zend_always_inline zend_string *zend_string_init(const char *str, size_t len, int persistent) { zend_string *ret = zend_string_alloc(len, persistent); memcpy(zstr_val(ret), str, len); zstr_val(ret)[len] = '\0'; return ret; }
\0
。该函数主要用于对字符串的扩容,注意这里扩容不会改变原来保存的值,只是把长度扩大到len。
static zend_always_inline zend_string *zend_string_extend(zend_string *s, size_t len, int persistent) { zend_string *ret; zend_assert(len >= zstr_len(s)); if (!zstr_is_interned(s)) { if (expected(gc_refcount(s) == 1)) { ret = (zend_string *)perealloc(s, zend_mm_aligned_size(_zstr_struct_size(len)), persistent); zstr_len(ret) = len; zend_string_forget_hash_val(ret); return ret; } else { gc_refcount(s)--; } } ret = zend_string_alloc(len, persistent); memcpy(zstr_val(ret), zstr_val(s), zstr_len(s) + 1); return ret; }
主要基于二进制安全对两个字符串进行判等,我们来看下php是怎么比较两个字符串的。
#define zend_string_equals_ci(s1, s2) \ (zstr_len(s1) == zstr_len(s2) && !zend_binary_strcasecmp(zstr_val(s1), zstr_len(s1), zstr_val(s2), zstr_len(s2)))
zend_api int zend_fastcall zend_binary_strcasecmp(const char *s1, size_t len1, const char *s2, size_t len2) /* {{{ */ { size_t len; int c1, c2; if (s1 == s2) { return 0; } len = min(len1, len2); while (len--) { c1 = zend_tolower_ascii(*(unsigned char *)s1++); c2 = zend_tolower_ascii(*(unsigned char *)s2++); if (c1 != c2) { return c1 - c2; } } return (int)(len1 - len2); }
感兴趣的同学可以到源码中查看。
如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复
YII2框架中使用RBAC对模块,控制器,方法的权限控制及规则的使用示例
YII2框架中ActiveDataProvider与GridView的配合使用操作示例
PhpStorm的使用教程(本地运行PHP+远程开发+快捷键)
网友评论