字符串结构
struct _zend_string{
zend_refcount_h gc; //8字节,gc引用计数
zend_ulong h; //8字节,字符串的哈希值(仅在作为数组key时用到)
size_t len; //8字节,记录字符串的长度,空间换时间,保证二进制安全
char var[1]; //1字节,柔性数组,字符串的值存储位置
}
- gc字段:存放引用计数,使得其和数组、对象一样,可以被多个zval引用
- h字段:存储字符串的哈希值,只有当字符串需要被作为数组的key时才会被初始化,同一个字符串被多次当做key使用时,不会重复计算其对应的哈希值
- len字段:记录字符串的长度,类型是size_t(long unsigned int),有两方面作用,一方面是用空间换时间,避免重复计算字符串长度,另一方面是为了保证二进制安全,和C语言不同,C语言字符串默认以\0结尾,所以不支持存储二进制数据,而PHP的字符串长度是通过len记录的,不会因为二进制数据中的\0而提前结束,所以可以存储二进制数据。
- val字段:存储字符串值,采用柔性数组,所谓柔性数组,就是结构体最后一个变量,占用末尾连续的一块内存。分配字符串内存时,一次申请内存大小为结构体大小+字符串长度+1(C字符串结尾追加\0),所以字符串的值与结构体中其他成员存储在同一块连续的空间中,在分配释放内存中可以直接当成整体处理,相比PHP5节省了一次读取字符串值时的内存读写。
智能字符串
typedef struct{
zend_string *s; //字符串值存储在zend_string.val中
size_t a; //存储申请的内存空间总大小
}smart_str
字符串申请内存时,会先申请一块较大的连续内存,申请的内存长度存储到smart_str中,把已使用的长度写到smart_str.s.len字段中,当字符串需要追加新字符串时,直接检查剩余内存块长度够不够,够了则直接追加,不够则重新申请一块更大的内存。通过空间换时间,避免了每次追加都需要重新申请内存。
字符串销毁
IN_STR_INTERNED标签标记的字符串,不会走PHP的内存池函数,这类字符串包含funciton、class、static、object等保留字符串,以及用户手动定义的字面字符串、变量名使用的字符串。在解析AST生成oparray时,会将这些字符串写入CG(interned_strings)数组中,从而避免重复浪费内存,且销毁方便(只需要遍历销毁interned_strings数组)。
- cli模式下的PHP进程:每次执行完都会调用php_module_shutdown,在这个阶段会调用zend_interned_strings_dtor函数销毁整个interned_strings数组。
- 未开启opcode下的PHP-FPM进程:fpm进程只有执行够max_request次数后才会执行php_module_shutdown,但是在php_request_shutdown阶段会调用zend_interned_restore_int方法销毁内部字符串。
- 开启opcache下的PHP-FPM进程:在php_request_shutown阶段不会销毁,常驻内存,只有在进程结束时才会被销毁