PHP是如何儲存變數的? zval結構體你了解嗎?
PHP 原始碼中的zval
在PHP 中定義一個變數是不需要宣告類型的,一開始給變數$a 一個整數值,後面又可以輕易地將其改變為其他類型。那在 PHP 的源碼中是如何來儲存這個變數 $a 的呢?帶著這個疑問我們一起去看 PHP 的原始碼。
PHP 的原始碼是由 C 編寫的,在 PHP 的原始碼中使用了一個 zval 的結構體來儲存在 PHP 程式碼中建立的變數。我們把 zval 結構體的定義拿出來簡單分析一下。
這是 PHP 在 Github 上的官方倉庫:github.com/php/php-src,本文使用的分支是 PHP-7.4.29。
zval 結構體
在PHP 的原始碼中找到這個檔案:php-src/Zend/zend_types.h,可以看到其中zval 結構體的定義如下,左側是原始碼。原始碼中使用了 PHP 自己定義的型別 zend_uchar 、uint16_t 、uint32_t 等,這些型別會針對不同平台和編譯器會轉為該平台下的 char short int 等。為了便於理解,我將其翻譯為普通類型並展示在了原始碼的右側。同時也把其中的巨集函數 ZEND_ENDIAN_LOHI_3() 也展開了。
typedef struct _zval_struct zval; ... 《源代码》 《翻译后》 ------------------------------------------------------------------------------------------- struct _zval_struct { | struct _zval_struct { zend_value value; | zend_value value; union { | union { struct { | struct { ZEND_ENDIAN_LOHI_3( | unsigned char type; zend_uchar type, | unsigned char type_flags; zend_uchar type_flags, | union { union { | unsigned short extra; uint16_t extra; | } u; } u | } v; ) | unsigned int type_info; } v; | } u1; uint32_t type_info; | union { } u1; | unsigned int next; union { | unsigned int cache_slot; uint32_t next; | unsigned int opline_num; uint32_t cache_slot; | unsigned int lineno; uint32_t opline_num; | unsigned int num_args; uint32_t lineno; | unsigned int fe_pos; uint32_t num_args; | unsigned int fe_iter_idx; uint32_t fe_pos; | unsigned int access_flags; uint32_t fe_iter_idx; | unsigned int property_guard; uint32_t access_flags; | unsigned int constant_flags; uint32_t property_guard; | unsigned int extra; uint32_t constant_flags; | } u2; uint32_t extra; | }; } u2; | }; |
在 zval 結構體中,變數的值就儲存在 zend_value 類型的 value 屬性中。並且透過 u1.v.type 來記錄這個值是什麼類型的,例如 IS_LONG 對應整數,IS_STRING 對應字串類型。
zend_value 聯合體
zend_value 類型也是在php-src/Zend/zend_types.h 中定義的,是一個聯合體,下面是zend_value 聯合體的定義,左側是原始碼。同樣在右側我也做了簡單的翻譯,把 zend_long uint32_t 翻譯為普通型別方便查看。
《源代码》 《翻译后》 ------------------------------------------------------------------------------------ typedef union _zend_value { | typedef union _zend_value { zend_long lval; /* long value */ | long lval; double dval; /* double value */ | double dval; zend_refcounted *counted; | zend_refcounted *counted; zend_string *str; | zend_string *str; zend_array *arr; | zend_array *arr; zend_object *obj; | zend_object *obj; zend_resource *res; | zend_resource *res; zend_reference *ref; | zend_reference *ref; zend_ast_ref *ast; | zend_ast_ref *ast; zval *zv; | zval *zv; void *ptr; | void *ptr; zend_class_entry *ce; | zend_class_entry *ce; zend_function *func; | zend_function *func; struct { | struct { uint32_t w1; | unsigned int w1; uint32_t w2; | unsigned int w2; } ww; | } ww; } zend_value; | } zend_value;
聯合體的一個特點是其佔用的記憶體是其屬性中最大類型對應的長度。其中的 zend_long 就是 long 類型,可以看到 long 類型的 lval 和 double 類型的 dval 所佔用的長度都是 8 個位元組。裡面其他指標類型,也都是 8 個位元組。最後面的結構體屬性 ww 是由兩個 int 型構成,長度相加也是 8 個位元組。因此此聯合體的長度為 8 個位元組。
在我們寫的 PHP 程式碼中,整數和浮點型資料的值會直接存放到 lval 和 dval 中。如果是字串、陣列以及其他類型時會開闢一段空間儲存數據,並將其位址存放在zend_value 中,也就是zval.value 屬性,如:zval.value.zend_long = 9527、zval.value.zend_string = 字元字串位址、zval.value.zend_array = 陣列位址。然後在 zval.u1.v.type 上標記這個 zval.value 是整數、或浮點型、或字串、或其他型別。
zval.u1.v.type 類型定義也是在php-src/Zend/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
zval 結構體記憶體佔用
接下來我們分析一下zval 所需佔用的記憶體。
value:zend_value 類型 8 個位元組。
u1:
u1.v.type:unsigned char 1 個位元組,u1.v.type_flags :unsigned char 1 個位元組,u1.v.u:聯合體中只有一個unsigned short 的extra 屬性2 個位元組,因此u1.v 的結構體總共是4 個位元組。
u1.type_info:unsigned int 4 個位元組。
因此 u1 這個聯合體的長度取最長的屬性的長度:4 個位元組。
u2:也是一個聯合體,裡面都是 int 型的屬性,因此長度是 4 個位元組。
所以 zval 總共佔用的記憶體是 8 4 4 = 16 個位元組。
也就是說當我們在寫PHP 程式碼時,如果創建了一個整數的變量,那麼實際上它在運行中會佔用16 個位元組的內存,內存開銷至少是C 語言的兩倍。當然這兩倍的開銷也帶來了 PHP 處理變數的彈性。
推薦學習:《PHP影片教學》
以上是PHP是如何儲存變數的? zval結構體你了解嗎?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

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

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

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

Dreamweaver CS6
視覺化網頁開發工具

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

熱門話題

PHP 8.4 帶來了多項新功能、安全性改進和效能改進,同時棄用和刪除了大量功能。 本指南介紹如何在 Ubuntu、Debian 或其衍生版本上安裝 PHP 8.4 或升級到 PHP 8.4

Visual Studio Code,也稱為 VS Code,是一個免費的原始碼編輯器 - 或整合開發環境 (IDE) - 可用於所有主要作業系統。 VS Code 擁有大量針對多種程式語言的擴展,可以輕鬆編寫

本教程演示瞭如何使用PHP有效地處理XML文檔。 XML(可擴展的標記語言)是一種用於人類可讀性和機器解析的多功能文本標記語言。它通常用於數據存儲

JWT是一種基於JSON的開放標準,用於在各方之間安全地傳輸信息,主要用於身份驗證和信息交換。 1.JWT由Header、Payload和Signature三部分組成。 2.JWT的工作原理包括生成JWT、驗證JWT和解析Payload三個步驟。 3.在PHP中使用JWT進行身份驗證時,可以生成和驗證JWT,並在高級用法中包含用戶角色和權限信息。 4.常見錯誤包括簽名驗證失敗、令牌過期和Payload過大,調試技巧包括使用調試工具和日誌記錄。 5.性能優化和最佳實踐包括使用合適的簽名算法、合理設置有效期、

字符串是由字符組成的序列,包括字母、數字和符號。本教程將學習如何使用不同的方法在PHP中計算給定字符串中元音的數量。英語中的元音是a、e、i、o、u,它們可以是大寫或小寫。 什麼是元音? 元音是代表特定語音的字母字符。英語中共有五個元音,包括大寫和小寫: a, e, i, o, u 示例 1 輸入:字符串 = "Tutorialspoint" 輸出:6 解釋 字符串 "Tutorialspoint" 中的元音是 u、o、i、a、o、i。總共有 6 個元

靜態綁定(static::)在PHP中實現晚期靜態綁定(LSB),允許在靜態上下文中引用調用類而非定義類。 1)解析過程在運行時進行,2)在繼承關係中向上查找調用類,3)可能帶來性能開銷。

PHP的魔法方法有哪些? PHP的魔法方法包括:1.\_\_construct,用於初始化對象;2.\_\_destruct,用於清理資源;3.\_\_call,處理不存在的方法調用;4.\_\_get,實現動態屬性訪問;5.\_\_set,實現動態屬性設置。這些方法在特定情況下自動調用,提升代碼的靈活性和效率。
