您好,登錄后才能下訂單哦!
這篇文章主要介紹PHP底層內核源碼之變量zend_string的示例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
我們主要通讀了_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 的寫時賦值 和 內存管理 以及字符串的各種操作的實現。所以你務必吸收上面的內容 并且打開源碼進行查看。
以上是“PHP底層內核源碼之變量zend_string的示例分析”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。