溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

PHP底層內(nèi)核源碼之變量zend_string的示例分析

發(fā)布時(shí)間:2021-06-11 10:44:38 來(lái)源:億速云 閱讀:234 作者:小新 欄目:編程語(yǔ)言

這篇文章主要介紹PHP底層內(nèi)核源碼之變量zend_string的示例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!

我們主要通讀了_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;     //獲取不同類(lèi)型結(jié)構(gòu)的gc頭部的指針
        zend_string      *str;        //string字符串 的指針
        zend_array       *arr;        //數(shù)組指針
        zend_object      *obj;        //object 對(duì)象指針
        zend_resource    *res;         ///資源類(lèi)型指針
        zend_reference   *ref;       //引用類(lèi)型指針   比如你通過(guò)&$c  定義的
        zend_ast_ref     *ast;     // ast 指針  線(xiàn)程安全 相關(guān)的 內(nèi)核使用的  
        zval             *zv;   // 指向另外一個(gè)zval的指針  內(nèi)核使用的
        void             *ptr;   //指針  ,通用類(lèi)型  內(nèi)核使用的
        zend_class_entry *ce;    //類(lèi) ,內(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類(lèi)型

再次回去打開(kāi) 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種類(lèi)型  其中前11種 是常用類(lèi)型 后面的類(lèi)型包含ast和 internal 等 不常用  后面到內(nèi)存管理 會(huì)依次展開(kāi) 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è)自定義類(lèi)型 )  h(也是一個(gè)自定義類(lèi)型) len (整型)   val[1](字符串類(lèi)型,但是這個(gè)名字怎么怪怪的)。

我們繼續(xù)跟gc 這個(gè)類(lèi)型

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ù)和字符串類(lèi)別存儲(chǔ)用的。

引用計(jì)數(shù)存放在refcount字段、字符串所屬的變量類(lèi)別則存儲(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)體有所不同 ,比如 陳雷大佬的書(shū)中 的_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 中

PHP底層內(nèi)核源碼之變量zend_string的示例分析

h是typedef uint64_t zend_ulong類(lèi)型的一個(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ù)和字符串類(lèi)型的記錄
	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ù)組有什么用途 ?
它的主要用途是為了滿(mǎn)足需要變長(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ù)組的好處很明顯,讀寫(xiě)字符串值時(shí)可以省一次內(nèi)存讀寫(xiě)

那為什么不用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ò)程是

PHP底層內(nèi)核源碼之變量zend_string的示例分析

(先不要考慮 詞法分析 語(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ù)的類(lèi)型為 int 值為 0 或 1。如果傳0,則通過(guò) Zend 內(nèi)存管理使用請(qǐng)求綁定的堆分配。這種分配在當(dāng)前請(qǐng)求結(jié)束后時(shí)銷(xiāo)毀。如果不銷(xiāo)毀,內(nèi)存就會(huì)泄漏。如果傳1,則要求了所謂的“持久”分配,將使用傳統(tǒng)的 C語(yǔ)言的malloc()調(diào)用。

說(shuō)人話(huà)就是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 畢竟要依賴(lài)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ù)組,類(lèi)型強(qiáng)制轉(zhuǎn)換為 void* 指針。
str2 -- 指向要復(fù)制的數(shù)據(jù)源,類(lèi)型強(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)開(kāi)頭的每個(gè)宏都會(huì)作用到 zend_string。
ZSTR_VAL()   訪(fǎng)問(wèn)字符數(shù)組 
ZSTR_LEN()  訪(fǎng)問(wèn)長(zhǎng)度信息 
ZSTR_HASH() 訪(fǎng)問(wèn)哈希值
…
以 Z_STR**(z) 開(kāi)頭的宏都會(huì)作用于到 zval 中的 zend_string 。
Z_STRVAL() 
Z_STRLEN()
Z_STRHASH()
…

這樣就開(kāi)辟了一個(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ì)依次展開(kāi)講解)

PHP底層內(nèi)核源碼之變量zend_string的示例分析

接下來(lái)的小節(jié)我們將繼續(xù) 分析zend_string 的寫(xiě)時(shí)賦值 和 內(nèi)存管理 以及字符串的各種操作的實(shí)現(xiàn)。所以你務(wù)必吸收上面的內(nèi)容 并且打開(kāi)源碼進(jìn)行查看。

以上是“PHP底層內(nèi)核源碼之變量zend_string的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI