這篇文章主要介紹PHP底層內(nèi)核源碼之變量zend_string的示例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
創(chuàng)新互聯(lián)服務(wù)項(xiàng)目包括蓮湖網(wǎng)站建設(shè)、蓮湖網(wǎng)站制作、蓮湖網(wǎng)頁(yè)制作以及蓮湖網(wǎng)絡(luò)營(yíng)銷策劃等。多年來(lái),我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,蓮湖網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到蓮湖省份的部分城市,未來(lái)相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
我們主要通讀了_zval_struct 來(lái)深入了解 PHP7以上版本的 變量實(shí)現(xiàn)和內(nèi)存占用
struct _zval_struct { zend_value value; u1; u2; };
其中 zend_value 結(jié)構(gòu)體的核心代碼如下
typedef union _zend_value { zend_long lval; //整型 double dval; //浮點(diǎn)型 zend_refcounted *counted; //獲取不同類型結(jié)構(gòu)的gc頭部的指針 zend_string *str; //string字符串 的指針 zend_array *arr; //數(shù)組指針 zend_object *obj; //object 對(duì)象指針 zend_resource *res; ///資源類型指針 zend_reference *ref; //引用類型指針 比如你通過(guò)&$c 定義的 zend_ast_ref *ast; // ast 指針 線程安全 相關(guān)的 內(nèi)核使用的 zval *zv; // 指向另外一個(gè)zval的指針 內(nèi)核使用的 void *ptr; //指針 ,通用類型 內(nèi)核使用的 zend_class_entry *ce; //類 ,內(nèi)核使用的 zend_function *func; // 函數(shù) ,內(nèi)核使用的 struct { uint32_t w1;//自己定義的。 無(wú)符號(hào)的32位整數(shù) uint32_t w2;//同上 } ww; } zend_value;
可以看出常用的 zend_value包含 上面幾種 會(huì)不會(huì)有個(gè)疑問(wèn) 怎么沒(méi)有布爾型呢?
其實(shí)這里這里的 zend_value 只是負(fù)責(zé)存儲(chǔ) 內(nèi)容 同樣你也會(huì)發(fā)現(xiàn) 也沒(méi)有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 等 不常用 后面到內(nèi)存管理 會(huì)依次展開 ast和 internal的使用
言歸正傳 在PHP中 管理字符串會(huì)使用zend_string
。每次 PHP 需要使用字符串時(shí),都會(huì)使用zend_string
結(jié)構(gòu), PHP沒(méi)有用原生c語(yǔ)言的 char 而是封裝了個(gè)結(jié)構(gòu)體
[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實(shí)際上是_zend_string的別名
別名是c語(yǔ)言特有的一種 形式
繼續(xù)跟到第235行 看到了 _zend_string是一個(gè)結(jié)構(gòu)體
struct _zend_string { zend_refcounted_h gc; zend_ulong h; /* hash value */ size_t len; char val[1]; };
這個(gè)結(jié)構(gòu)體包含 4個(gè)部分
其中 有g(shù)c (這顯然又是一個(gè)自定義類型 ) h(也是一個(gè)自定義類型) len (整型) val[1](字符串類型,但是這個(gè)名字怎么怪怪的)。
我們繼續(xù)跟gc 這個(gè)類型
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結(jié)構(gòu)體的別名
這個(gè)結(jié)構(gòu)體 包括 一個(gè) 32位純數(shù)字的 refcount 和一個(gè)聯(lián)合體u 聯(lián)合體u里面包括一個(gè) type_info zend_refcounted_h 占用8字節(jié) ,refount英文翻譯成中文是引用的意思 顯然 這個(gè) zend_refcounted_h是為了引用計(jì)數(shù)和字符串類別存儲(chǔ)用的。
引用計(jì)數(shù)存放在refcount字段、字符串所屬的變量類別則存儲(chǔ)在type字段。zend_string結(jié)構(gòu)體中因?yàn)榧尤肓薵c字段,使得其和數(shù)組、對(duì)象一樣可被多個(gè)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 你如果看過(guò)其他大佬做的源碼文章會(huì)發(fā)現(xiàn)跟我這個(gè)版本的_zend_refcounted_h 結(jié)構(gòu)體有所不同 ,比如 陳雷大佬的書中 的_zend_refcounted_h結(jié)構(gòu)體會(huì)包含一個(gè)聯(lián)合體 聯(lián)合體里面又有用于垃圾回收顏色用的 gc_info 等 *************************************
個(gè)人認(rèn)為是因?yàn)?zend_zval 的u1 已經(jīng)包含了 type_flags type 等字段 所以在PHP7.4版本里zend_refcounted_h 就棄用了這些值
在 zend_string結(jié)構(gòu)體 第二個(gè)值 h 指向了zend_ulong
通過(guò)追蹤代碼 發(fā)現(xiàn) zendulong 在 zend_long.h 中
h是typedef uint64_t zend_ulong類型的一個(gè)變量,保存字符串對(duì)應(yīng)的哈希值,其后續(xù)會(huì)用在數(shù)組里面。他占用8個(gè)字節(jié)
我們把 zend_string 加上注釋
struct _zend_string { zend_refcounted_h gc; //占用8個(gè)字節(jié) 用于gc的計(jì)數(shù)和字符串類型的記錄 zend_ulong h; // 占用8個(gè)字節(jié) 用于記錄 字符串的哈希值 size_t len; //占用8個(gè)字節(jié) 字符串的長(zhǎng)度 char val[1]; //占用1個(gè)字節(jié) 字符串的值存儲(chǔ)位置 };
len和val[1]用于標(biāo)識(shí)字符串,c語(yǔ)言中字符串的表示形式可以以\0結(jié)尾,通過(guò)遍歷得到字符串長(zhǎng)度,但是其非二進(jìn)制安全,如字符串中本身就包含\0,那么該字符串\0后面的字符串會(huì)被截?cái)?,這里len用于保存字符串的長(zhǎng)度, val是一個(gè)柔性數(shù)組。實(shí)現(xiàn)的字符串是二進(jìn)制安全的。
關(guān)于\0 可以看以下 c語(yǔ)言代碼
main(){ char a[] = "aa\0"; char b[] = "aa\0aaaaaaaaaaaaaaaaaa"; printf(strlen(a)); printf(strlen(b)); }
運(yùn)行結(jié)果為 2 2
也就是說(shuō)C語(yǔ)言認(rèn)為a和b這兩個(gè)字符串是相等的,而且ab的長(zhǎng)度為都為2
但是在PHP中因?yàn)橛辛藌end_string的存在 可以做到二進(jìn)制安全
例如,字符串 “foo” 在zend_string中存儲(chǔ)為 “foo\0”,且它的長(zhǎng)度為3。另外,字符串 “foo\0bar” 將存儲(chǔ)為 “foo\0bar\0”,且其長(zhǎng)度為7。
至于什么是柔性數(shù)組 參考goole搜的介紹
1、什么是柔性數(shù)組? 柔性數(shù)組既數(shù)組大小待定的數(shù)組, C語(yǔ)言中結(jié)構(gòu)體的最后一個(gè)元素可以是大小未知的數(shù)組,也就是所謂的0長(zhǎng)度, 所以我們可以用結(jié)構(gòu)體來(lái)創(chuàng)建柔性數(shù)組。 2、柔性數(shù)組有什么用途 ? 它的主要用途是為了滿足需要變長(zhǎng)度的結(jié)構(gòu)體,為了解決使用數(shù)組時(shí)內(nèi)存的冗余和數(shù)組的越界問(wèn)題。 3、用法 :在一個(gè)結(jié)構(gòu)體的最后 ,申明一個(gè)長(zhǎng)度為空的數(shù)組,就可以使得這個(gè)結(jié)構(gòu)體是可變長(zhǎng)的。 對(duì)于編譯器來(lái)說(shuō),此時(shí)長(zhǎng)度為0的數(shù)組并不占用空間,因?yàn)閿?shù)組名 本身不占空間,它只是一個(gè)偏移量, 數(shù)組名這個(gè)符號(hào)本身代 表了一個(gè)不可修改的地址常量 (注意:數(shù)組名永遠(yuǎn)都不會(huì)是指針! ),但對(duì)于這個(gè)數(shù)組的大小,我們 可以進(jìn)行動(dòng)態(tài)分配,對(duì)于編譯器而言,數(shù)組名僅僅是一個(gè)符號(hào), 它不會(huì)占用任何空間,它在結(jié)構(gòu)體中,只是代表了一個(gè)偏移量,代表一個(gè)不可修改的地址常量! 對(duì)于柔性數(shù)組的這個(gè)特點(diǎn),很容易構(gòu)造出變成結(jié)構(gòu)體,如緩沖區(qū),數(shù)據(jù)包等等
用柔性數(shù)組的好處很明顯,讀寫字符串值時(shí)可以省一次內(nèi)存讀寫
那為什么不用val[0] 或者var[] 而是var[1] 呢 因?yàn)?為了兼容c99的標(biāo)準(zhǔn) c99里不允許變長(zhǎng)數(shù)組的定義,但是支持var[1] 你可以理解為 為了兼容不同版本的c編譯器即可。
len字段是記錄 字符串的長(zhǎng)度 跟上面的柔性數(shù)組一配合就知道 字符串的真實(shí)長(zhǎng)度了 讀取的數(shù)據(jù)長(zhǎng)度以自身結(jié)構(gòu)體len值為準(zhǔn)。同時(shí)這也是典型的空間換時(shí)間算法 也節(jié)省了還要去計(jì)算字符串的長(zhǎng)度的消耗。
所以 zend_string 結(jié)構(gòu)體整體占用 25個(gè)字節(jié) 但是因?yàn)閮?nèi)存對(duì)齊 所以占用32個(gè)字節(jié)
以上你已經(jīng)掌握了 字符串 結(jié)構(gòu)體的 基礎(chǔ)知識(shí)
在PHP中 封裝了很多 操作字符串的基礎(chǔ)宏 一般在 zend_string.h 中
下面這行代碼 php是怎么實(shí)現(xiàn)的?
其實(shí)整個(gè)過(guò)程是
(先不要考慮 詞法分析 語(yǔ)法分析 AST 等過(guò)程)
<?php $str = 'PHP'; printf("字符串內(nèi)容為".$str); printf("字符串長(zhǎng)度為".strlen($str)); ?>
其實(shí)對(duì)應(yīng)的 ‘偽代碼’如下
zend_string *s; zend_string_init(s,"PHP", strlen("PHP"), 0) // 其中 zend_string_init 為初始化一個(gè)普通字符串 s // 存儲(chǔ)字符串到s 到變量 zval a 中 ZVAL_STR(&a, s); php_printf("子字符串內(nèi)容為", Z_STRVAL(a)); php_printf("字符串長(zhǎng)度為", Z_STRLEN(a)); zend_string_release(a);
zend_string_init()
函數(shù)(實(shí)際上是宏)計(jì)算完整的char *
字符串和它的長(zhǎng)度。最后一個(gè)參數(shù)的類型為 int 值為 0 或 1。如果傳0,則通過(guò) Zend 內(nèi)存管理使用請(qǐng)求綁定的堆分配。這種分配在當(dāng)前請(qǐng)求結(jié)束后時(shí)銷毀。如果不銷毀,內(nèi)存就會(huì)泄漏。如果傳1,則要求了所謂的“持久”分配,將使用傳統(tǒng)的 C語(yǔ)言的malloc()
調(diào)用。
說(shuō)人話就是zend_string_init函數(shù)把一個(gè)普通字符串初始化成zend_string
在zend_string.h 中 第152行 可以找到
//上述我們傳進(jìn)來(lái) zend_string_init("PHP", 3, 0); static zend_always_inline zend_string *zend_string_init(const char *str, size_t len, int persistent) { //分配內(nèi)存及初始化 初始化內(nèi)存的值 zend_string *ret = zend_string_alloc(len, persistent); //拷貝 str 到 zend_string 中的val中 memcpy(ZSTR_VAL(ret), str, len); //把字符串末尾加上\0 畢竟要依賴c語(yǔ)言 所以最最底層要按照人家規(guī)則走 ZSTR_VAL(ret)[len] = '\0'; return ret; }
zend_string_init 第一步 又調(diào)用了 zend_string_alloc 然后進(jìn)行 memcpy 執(zhí)行ZSTR_VAL
最后返回一個(gè) 字符串變量
下面是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; }
這個(gè)宏代碼主要是申請(qǐng)一塊連續(xù)的內(nèi)存,內(nèi)存的大小的計(jì)算公式為:實(shí)際申請(qǐng)大小= 結(jié)構(gòu)體的大?。?4) + 字符串的長(zhǎng)度(len)+1,實(shí)際申請(qǐng)大小是按照8字節(jié)對(duì)齊的,不一定等于實(shí)際計(jì)算的結(jié)果。 len = string.len + new_str_len + string_struct_len + 1
這個(gè)+1就是為了追加 \0 使用的
并且還做了初始化 zend_string 工作
//這是個(gè)宏 設(shè)置 zend_string 中的 h值 還記得h值是干嘛的嗎? ZSTRH(ret) = 0; //這是個(gè)宏 設(shè)置 zend_string 中的len的值 ZSTR_LEN(ret) = len;
然后進(jìn)行memcpy 函數(shù)
C 庫(kù)函數(shù) 中的memcpy() void *memcpy(void *str1, const void *str2, size_t n) 參數(shù) str1 -- 指向用于存儲(chǔ)復(fù)制內(nèi)容的目標(biāo)數(shù)組,類型強(qiáng)制轉(zhuǎn)換為 void* 指針。 str2 -- 指向要復(fù)制的數(shù)據(jù)源,類型強(qiáng)制轉(zhuǎn)換為 void* 指針。 n -- 要被復(fù)制的字節(jié)數(shù)。 返回值 該函數(shù)返回一個(gè)指向目標(biāo)存儲(chǔ)區(qū) str1 的指針
memcpy主要用于拷貝數(shù)據(jù) 里面包含了一個(gè)宏 ZSTR_VAL
這個(gè)宏是設(shè)置zend_string的val中數(shù)據(jù)
通過(guò)閱讀源碼我們可以發(fā)現(xiàn) 以ZSTR_***(s)開頭的每個(gè)宏都會(huì)作用到 zend_string。 ZSTR_VAL() 訪問(wèn)字符數(shù)組 ZSTR_LEN() 訪問(wèn)長(zhǎng)度信息 ZSTR_HASH() 訪問(wèn)哈希值 … 以 Z_STR**(z) 開頭的宏都會(huì)作用于到 zval 中的 zend_string 。 Z_STRVAL() Z_STRLEN() Z_STRHASH() …
這樣就開辟了一個(gè)字符串 值為 "PHP"
下一步又是一個(gè)宏 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); } } }
顯然是用于釋放內(nèi)存的
關(guān)于zend_string 的宏 可以參考以下注釋 (慢慢會(huì)依次展開講解)
接下來(lái)的小節(jié)我們將繼續(xù) 分析zend_string 的寫時(shí)賦值 和 內(nèi)存管理 以及字符串的各種操作的實(shí)現(xiàn)。所以你務(wù)必吸收上面的內(nèi)容 并且打開源碼進(jìn)行查看。
以上是“PHP底層內(nèi)核源碼之變量zend_string的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!
新聞名稱:PHP底層內(nèi)核源碼之變量zend_string的示例分析
瀏覽路徑:http://jinyejixie.com/article2/jjhjoc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供小程序開發(fā)、ChatGPT、網(wǎng)站設(shè)計(jì)公司、網(wǎng)頁(yè)設(shè)計(jì)公司、網(wǎng)站策劃、外貿(mào)建站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)