分析PHP底層內核原始碼之變數 (二) zend_string
本篇文章给大家介绍《分析PHP底层内核源码之变量 (二) zend_string》。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。
相关文章推荐:《解析PHP底层内核源码之变量 (一)》《分析PHP底层内核源码之变量 (三)》
在变量(一)中 我们主要通读了_zval_struct 来深入了解 PHP7以上版本的 变量实现和内存占用
struct _zval_struct { zend_value value; u1; u2; };
其中 zend_value 结构体的核心代码如下
typedef union _zend_value { zend_long lval; //整型 double dval; //浮点型 zend_refcounted *counted; //获取不同类型结构的gc头部的指针 zend_string *str; //string字符串 的指针 zend_array *arr; //数组指针 zend_object *obj; //object 对象指针 zend_resource *res; ///资源类型指针 zend_reference *ref; //引用类型指针 比如你通过&$c 定义的 zend_ast_ref *ast; // ast 指针 线程安全 相关的 内核使用的 zval *zv; // 指向另外一个zval的指针 内核使用的 void *ptr; //指针 ,通用类型 内核使用的 zend_class_entry *ce; //类 ,内核使用的 zend_function *func; // 函数 ,内核使用的 struct { uint32_t w1;//自己定义的。 无符号的32位整数 uint32_t w2;//同上 } ww; } zend_value;
可以看出常用的 zend_value包含 上面几种 会不会有个疑问 怎么没有布尔型呢?
其实这里这里的 zend_value 只是负责存储 内容 同样你也会发现 也没有null类型
再次回去打开 zend_types.h
[root@2890cf458ee2 Zend]# vim zend_types.h /* regular data types */ #define IS_UNDEF 0 #define IS_NULL 1 #define IS_FALSE 2 #define IS_TRUE 3 #define IS_LONG 4 #define IS_DOUBLE 5 #define IS_STRING 6 #define IS_ARRAY 7 #define IS_OBJECT 8 #define IS_RESOURCE 9 #define IS_REFERENCE 10 /* constant expressions */ #define IS_CONSTANT_AST 11 /* internal types */ #define IS_INDIRECT 13 #define IS_PTR 14 #define IS_ALIAS_PTR 15 #define _IS_ERROR 15 /* fake types used only for type hinting (Z_TYPE(zv) can not use them) */ #define _IS_BOOL 16 #define IS_CALLABLE 17 #define IS_ITERABLE 18 #define IS_VOID 19 #define _IS_NUMBER 20
可以看到 在代码里 定义了 20种类型 其中前11种 是常用类型 后面的类型包含ast和 internal 等 不常用 后面到内存管理 会依次展开 ast和 internal的使用
言归正传 在PHP中 管理字符串会使用zend_string
。每次 PHP 需要使用字符串时,都会使用zend_string
结构, PHP没有用原生c语言的 char 而是封装了个结构体
[root@2890cf458ee2 Zend]# vim zend_types.h
82 typedef struct _zend_object_handlers zend_object_handlers; 83 typedef struct _zend_class_entry zend_class_entry; 84 typedef union _zend_function zend_function; 85 typedef struct _zend_execute_data zend_execute_data; 86 87 typedef struct _zval_struct zval; 88 89 typedef struct _zend_refcounted zend_refcounted; 90 typedef struct _zend_string zend_string; 91 typedef struct _zend_array zend_array; 92 typedef struct _zend_object zend_object; 93 typedef struct _zend_resource zend_resource; 94 typedef struct _zend_reference zend_reference; 95 typedef struct _zend_ast_ref zend_ast_ref; 96 typedef struct _zend_ast zend_ast;
在第90行看到 zend_string实际上是_zend_string的别名
别名是c语言特有的一种 形式
继续跟到第235行 看到了 _zend_string是一个结构体
struct _zend_string { zend_refcounted_h gc; zend_ulong h; /* hash value */ size_t len; char val[1]; };
这个结构体包含 4个部分
其中 有gc (这显然又是一个自定义类型 ) h(也是一个自定义类型) len (整型) val[1](字符串类型,但是这个名字怎么怪怪的)。
我们继续跟gc 这个类型
typedef struct _zend_refcounted_h { uint32_t refcount; /* reference counter 32-bit */ union { uint32_t type_info; } u; } zend_refcounted_h;
可以看到 zend_refcounted_h 是 _zend_refcounted_h结构体的别名
这个结构体 包括 一个 32位纯数字的 refcount 和一个联合体u 联合体u里面包括一个 type_info zend_refcounted_h 占用8字节 ,refount英文翻译成中文是引用的意思 显然 这个 zend_refcounted_h是为了引用计数和字符串类别存储用的。
引用计数存放在refcount字段、字符串所属的变量类别则存储在type字段。zend_string结构体中因为加入了gc字段,使得其和数组、对象一样可被多个zval引用 这非常巧妙了。
[root@2890cf458ee2 Zend]# vim zend_types.h [root@2890cf458ee2 Zend]# php -v PHP 7.4.15 (cli) (built: Feb 22 2021 08:46:50) ( NTS ) Copyright (c) The PHP Group Zend Engine v3.4.0, Copyright (c) Zend Technologies **************************************** 我的版本为 7.4.15 你如果看过其他大佬做的源码文章会发现跟我这个版本的_zend_refcounted_h 结构体有所不同 ,比如 陈雷大佬的书中 的_zend_refcounted_h结构体会包含一个联合体 联合体里面又有用于垃圾回收颜色用的 gc_info 等 *************************************
个人认为是因为 zend_zval 的u1 已经包含了 type_flags type 等字段 所以在PHP7.4版本里zend_refcounted_h 就弃用了这些值
在 zend_string结构体 第二个值 h 指向了zend_ulong
通过追踪代码 发现 zendulong 在 zend_long.h 中

h是typedef uint64_t zend_ulong类型的一个变量,保存字符串对应的哈希值,其后续会用在数组里面。他占用8个字节
我们把 zend_string 加上注释
struct _zend_string { zend_refcounted_h gc; //占用8个字节 用于gc的计数和字符串类型的记录 zend_ulong h; // 占用8个字节 用于记录 字符串的哈希值 size_t len; //占用8个字节 字符串的长度 char val[1]; //占用1个字节 字符串的值存储位置 };
len和val[1]用于标识字符串,c语言中字符串的表示形式可以以\0结尾,通过遍历得到字符串长度,但是其非二进制安全,如字符串中本身就包含\0,那么该字符串\0后面的字符串会被截断,这里len用于保存字符串的长度, val是一个柔性数组。实现的字符串是二进制安全的。
关于\0 可以看以下 c语言代码
main(){ char a[] = "aa\0"; char b[] = "aa\0aaaaaaaaaaaaaaaaaa"; printf(strlen(a)); printf(strlen(b)); }
运行结果为 2 2
也就是说C语言认为a和b这两个字符串是相等的,而且ab的长度为都为2
但是在PHP中因为有了zend_string的存在 可以做到二进制安全
例如,字符串 “foo” 在zend_string中存储为 “foo\0”,且它的长度为3。另外,字符串 “foo\0bar” 将存储为 “foo\0bar\0”,且其长度为7。
至于什么是柔性数组 参考goole搜的介绍
1、什么是柔性数组? 柔性数组既数组大小待定的数组, C语言中结构体的最后一个元素可以是大小未知的数组,也就是所谓的0长度, 所以我们可以用结构体来创建柔性数组。 2、柔性数组有什么用途 ? 它的主要用途是为了满足需要变长度的结构体,为了解决使用数组时内存的冗余和数组的越界问题。 3、用法 :在一个结构体的最后 ,申明一个长度为空的数组,就可以使得这个结构体是可变长的。 对于编译器来说,此时长度为0的数组并不占用空间,因为数组名 本身不占空间,它只是一个偏移量, 数组名这个符号本身代 表了一个不可修改的地址常量 (注意:数组名永远都不会是指针! ),但对于这个数组的大小,我们 可以进行动态分配,对于编译器而言,数组名仅仅是一个符号, 它不会占用任何空间,它在结构体中,只是代表了一个偏移量,代表一个不可修改的地址常量! 对于柔性数组的这个特点,很容易构造出变成结构体,如缓冲区,数据包等等
用柔性数组的好处很明显,读写字符串值时可以省一次内存读写
那为什么不用val[0] 或者var[] 而是var[1] 呢 因为 为了兼容c99的标准 c99里不允许变长数组的定义,但是支持var[1] 你可以理解为 为了兼容不同版本的c编译器即可。
len字段是记录 字符串的长度 跟上面的柔性数组一配合就知道 字符串的真实长度了 读取的数据长度以自身结构体len值为准。同时这也是典型的空间换时间算法 也节省了还要去计算字符串的长度的消耗。
所以 zend_string 结构体整体占用 25个字节 但是因为内存对齐 所以占用32个字节
以上你已经掌握了 字符串 结构体的 基础知识
在PHP中 封装了很多 操作字符串的基础宏 一般在 zend_string.h 中
下面这行代码 php是怎么实现的?
其实整个过程是

(先不要考虑 词法分析 语法分析 AST 等过程)
<?php $str = 'PHP'; printf("字符串内容为".$str); printf("字符串长度为".strlen($str)); ?>
其实对应的 ‘伪代码’如下
zend_string *s; zend_string_init(s,"PHP", strlen("PHP"), 0) // 其中 zend_string_init 为初始化一个普通字符串 s // 存储字符串到s 到变量 zval a 中 ZVAL_STR(&a, s); php_printf("子字符串内容为", Z_STRVAL(a)); php_printf("字符串长度为", Z_STRLEN(a)); zend_string_release(a);
zend_string_init()
函数(实际上是宏)计算完整的char *
字符串和它的长度。最后一个参数的类型为 int 值为 0 或 1。如果传0,则通过 Zend 内存管理使用请求绑定的堆分配。这种分配在当前请求结束后时销毁。如果不销毁,内存就会泄漏。如果传1,则要求了所谓的“持久”分配,将使用传统的 C语言的malloc()
调用。
说人话就是zend_string_init函数把一个普通字符串初始化成zend_string
在zend_string.h 中 第152行 可以找到
//上述我们传进来 zend_string_init("PHP", 3, 0); 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); //拷贝 str 到 zend_string 中的val中 memcpy(ZSTR_VAL(ret), str, len); //把字符串末尾加上\0 毕竟要依赖c语言 所以最最底层要按照人家规则走 ZSTR_VAL(ret)[len] = '\0'; return ret; }
zend_string_init 第一步 又调用了 zend_string_alloc 然后进行 memcpy 执行ZSTR_VAL
最后返回一个 字符串变量
下面是zend_string_alloc的代码
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); GC_SET_REFCOUNT(ret, 1); GC_TYPE_INFO(ret) = IS_STRING | ((persistent ? IS_STR_PERSISTENT : 0) << GC_FLAGS_SHIFT); ZSTR_H(ret) = 0; ZSTR_LEN(ret) = len; return ret; }
这个宏代码主要是申请一块连续的内存,内存的大小的计算公式为:实际申请大小= 结构体的大小(24) + 字符串的长度(len)+1,实际申请大小是按照8字节对齐的,不一定等于实际计算的结果。 len = string.len + new_str_len + string_struct_len + 1
这个+1就是为了追加 \0 使用的
并且还做了初始化 zend_string 工作
//这是个宏 设置 zend_string 中的 h值 还记得h值是干嘛的吗? ZSTRH(ret) = 0; //这是个宏 设置 zend_string 中的len的值 ZSTR_LEN(ret) = len;
然后进行memcpy 函数
C 库函数 中的memcpy() void *memcpy(void *str1, const void *str2, size_t n) 参数 str1 -- 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。 str2 -- 指向要复制的数据源,类型强制转换为 void* 指针。 n -- 要被复制的字节数。 返回值 该函数返回一个指向目标存储区 str1 的指针
memcpy主要用于拷贝数据 里面包含了一个宏 ZSTR_VAL
这个宏是设置zend_string的val中数据
通过阅读源码我们可以发现 以ZSTR_***(s)开头的每个宏都会作用到 zend_string。 ZSTR_VAL() 访问字符数组 ZSTR_LEN() 访问长度信息 ZSTR_HASH() 访问哈希值 … 以 Z_STR**(z) 开头的宏都会作用于到 zval 中的 zend_string 。 Z_STRVAL() Z_STRLEN() Z_STRHASH() …
这样就开辟了一个字符串 值为 "PHP"
下一步又是一个宏 zend_string_release
static zend_always_inline void zend_string_release(zend_string *s) { if (!ZSTR_IS_INTERNED(s)) { if (GC_DELREF(s) == 0) { pefree(s, GC_FLAGS(s) & IS_STR_PERSISTENT); } } }
显然是用于释放内存的
关于zend_string 的宏 可以参考以下注释 (慢慢会依次展开讲解)

接下来的小节我们将继续 分析zend_string 的写时赋值 和 内存管理 以及字符串的各种操作的实现。所以你务必吸收上面的内容 并且打开源码进行查看
感谢陈雷前辈的《PHP7源码底层设计与实现》
▏本文经原作者PHP崔雪峰同意,发布在php中文网,原文地址:https://zhuanlan.zhihu.com/p/352830733
以上是分析PHP底層內核原始碼之變數 (二) zend_string的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

PHP7底层开发原理解密:探索PHP内存管理的策略和技术近年来,PHP7的性能得到了显著的提升,这主要得益于其改进的底层开发原理。其中,PHP内存管理成为一个重要的关注点,它对于PHP脚本的执行效率和性能起着至关重要的作用。本文将揭示PHP内存管理的策略和技术,并通过代码示例进行详细解释。在PHP5的时代,PHP采用了引用计数(ReferenceCount

PHP底層的高效能Web應用架構設計與實現在當今網路時代,PHP已成為最受歡迎的Web開發語言之一,它簡單易學、開發快速、運作穩定且擁有強大的社群支援。不過在處理高並發的Web應用場景下,PHP的效能問題成為開發者必須面對的挑戰,因此進行PHP底層的高效能Web應用架構設計和實作是前端開發領域中至關重要的一步。本文將介紹如何透過設計和實現一個高效能的PH

深入了解PHP底層開發原理:檔案上傳和下載隨著Web應用的廣泛應用,檔案的上傳和下載成為了常見的功能需求。在PHP中,我們可以透過一些內建函數和類別來實現檔案上傳和下載的功能。然而,為了更好地理解底層開發原理,我們可以深入了解PHP檔案上傳和下載的實作原理。文件上傳檔案上傳是指將本機上的檔案傳輸到網路伺服器上的過程。在PHP中,檔案上傳是透過HTTP協

深入研究PHP底層開發原理:伺服器部署和效能監控隨著網路的快速發展,PHP作為一種高效且方便的伺服器端腳本語言,被廣泛應用於Web開發領域。身為PHP開發人員,了解PHP底層開發原理,特別是伺服器部署和效能監控的知識,對於提高應用程式的效能和穩定性至關重要。一、伺服器部署硬體環境選擇在進行伺服器部署前,首先要選擇合適的硬體環境。根據應用程式的規模和

PHP底層開發原理指南:效能測試和負載平衡在Web應用程式開發中,PHP是廣泛使用的程式語言之一。為了提高PHP應用程式的效能和可擴展性,了解PHP底層開發原理是必不可少的。本文將重點放在效能測試和負載平衡兩個方面,並附上相關的程式碼範例,以幫助讀者更好地理解和應用這些原理。效能測試效能測試是評估應用程式在各種負載條件下的效能和穩定性的過程。在PHP開發中,常

PHP底層的執行緒池與協程實作方法在PHP程式設計中,執行緒池和協程是提高效能和並發能力的重要方法。本文將介紹PHP底層實作執行緒池和協程的方法,並提供具體程式碼範例。一、執行緒池的實作執行緒池是一種重複使用執行緒的機制,可以提高多執行緒應用程式的效能。在PHP中,使用多執行緒可以實現並發執行多個任務,提高程式的並發能力。以下是一個簡單的PHP執行緒池的實作範例:classThrea

PHP底層的資料結構與演算法最佳化,需要具體程式碼範例隨著網路的快速發展,PHP作為一種常用的伺服器端腳本語言,被廣泛應用於Web開發領域。在大型Web應用中,效能的最佳化是至關重要的一步。而對PHP底層的資料結構和演算法進行最佳化,可以提高程式的效率,在大量資料處理和複雜演算法運算的場景下,尤其重要。 PHP底層的資料結構與演算法的最佳化,可以從多個面向入手:數組與鍊錶的選

研究PHP底層開發原理:安全性評估與漏洞修復導語:作為最受歡迎的伺服器端腳本語言之一,PHP持續發展並在Web開發中扮演著重要的角色。然而,正因其廣泛應用,PHP也成為網路攻擊的目標。為了確保應用程式的安全性,對PHP底層開發原理的研究不可或缺。本文將探討PHP安全性評估和漏洞修復的重要性,並介紹一些常見的PHP漏洞類型和修復方法。 PHP安全性評估的重要性P
