中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

PHP底層內核源碼之變量zend_string的示例分析

發布時間:2021-06-11 10:44:38 來源:億速云 閱讀:240 作者:小新 欄目:編程語言

這篇文章主要介紹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 中

PHP底層內核源碼之變量zend_string的示例分析

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是怎么實現的?

其實整個過程是

PHP底層內核源碼之變量zend_string的示例分析

(先不要考慮 詞法分析 語法分析 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 的宏 可以參考以下注釋 (慢慢會依次展開講解)

PHP底層內核源碼之變量zend_string的示例分析

接下來的小節我們將繼續 分析zend_string 的寫時賦值 和 內存管理 以及字符串的各種操作的實現。所以你務必吸收上面的內容 并且打開源碼進行查看。

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

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

类乌齐县| 九江市| 海盐县| 新郑市| 扶风县| 康乐县| 称多县| 会理县| 普兰县| 新巴尔虎右旗| 共和县| 黔西| 临桂县| 高州市| 科尔| 枝江市| 台湾省| 监利县| 禄丰县| 沁水县| 靖江市| 遵义市| 贵南县| 阿克| 棋牌| 恭城| 溧阳市| 深圳市| 门源| 二手房| 满城县| 西青区| 左贡县| 西华县| 海阳市| 太和县| 叶城县| 长阳| 明光市| 宜兰市| 南平市|