搜尋
首頁後端開發PHP7解析PHP7內核之變數的內部實現

解析PHP7內核之變數的內部實現

PHP變數實現的基礎結構是zval,各種類型的實作皆基於此結構實現,是PHP中最基礎的一個結構,每個PHP變數都對應一個zval,就看下面這個結構以及PHP變數的記憶體管理機制。

zval結構

#相關學習推薦:PHP 程式設計從入門到精通

zval结构比较简单,内嵌一个union类型的zend_value保存具体变量类型的值或指针,zval中还有两个union:u1、u2:
  • u1:它的意義比較直觀,變數的型別就透過u1.type區分,另外一個值type_flags為型別掩碼,在變數的記憶體管理、gc機制中會用到,第三部分會詳細分析,至於後面兩個const_flagsreserved暫且不管

  • u2:這個值純粹是輔助值,假如zval只有: valueu1兩個值,整個zval的大小也會對齊到16byte,既然不管有沒有u2大小都是16byte,把多餘的4byte拿出來用於一些特殊用途還是很划算的,例如next在雜湊表解決哈希衝突時會用到,還有fe_pos在foreach會用到......

zend_value可以看出,除longdouble型別直接儲存值外,其它型別都為指針,指向各自的結構。


 型別

zval.u1.type類型:

#

標量類型

最簡單的類型是true、false、long、double、null,其中true、false、null沒有value,直接根據type區分,而long、double的值則直接存在value中:zend_long、double,也就是標量類型不需要額外的value指標。

字串

PHP中字串透過zend_string表示:

  • gc:變數引用信息,例如目前value的引用數,所有用到引用計數的變數型別都會有這個結構,3.1節會詳細分析

  • h:雜湊值,陣列中計算索引時會用到

  • #len:字串長度,經由這個值保證二進位安全性

  • val:字串內容,變長struct,分配時按len長度申請記憶體

事實上字串又可具體分為幾類:IS_STR_PERSISTENT(透過malloc分配的)、IS_STR_INTERNED(php程式碼裡寫的一些字面量,例如函式名稱、變數值)、IS_STR_PERMANENT(永久值,生命週期大於request) 、IS_STR_CONSTANT(常數)、IS_STR_CONSTANT_UNQUALIFIED,這個資訊透過flag保存:zval.value->gc.u.flags,後面用到的時候再具體分析。

陣列

array是PHP中非常強大的資料結構,它的底層實作就是普通有序的HashTable,這裡簡單看下它的結構,下一節會單獨分析數組的實作。


物件/資源


#物件比較常見,資源指的是tcp連接、文件句柄等等類型,這種類型比較靈活,可以隨意定義struct,透過ptr指向,後面會單獨分析這種類型,這裡不再多說。


#引用

#引用是PHP中比較特殊的一種類型,它實際上是指向另外一個PHP變量,對它的修改會直接改變實際指向的zval,可以簡單的理解為C中的指針,在PHP中通過&操作符產生一個引用變量,也就是說不管以前的類型是什麼,&首先會將新產生一個zval,類型為IS_REFERENCE,然後將val的value指向原來zval的value。

結構非常簡單,除了公共部分zend_refcounted_h外只有一個val,舉個範例看下具體的結構關係:

最終的結果如圖:

#


注意:引用只能透過&##產生,無法透過賦值傳遞,例如:



$b = &$a這時候$a$b的型別是引用,但$c = $ b並不會直接將$b賦值給$c,而是把$b實際指向的zval賦值給$c,如果想要

$c

也是一個引用則需要這麼操作:

################################這個也表示PHP中的###引用只可能有一層###,###不會出現一個引用指向另外一個引用的情況###,也就是沒有C語言中###指標的指標## #的概念。 #########記憶體管理#########接下來分析下變數的分配、銷毀。 ###

在分析变量内存管理之前我们先自己想一下可能的实现方案,最简单的处理方式:定义变量时alloc一个zval及对应的value结构(ref/arr/str/res...),赋值、函数传参时硬拷贝一个副本,这样各变量最终的值完全都是独立的,不会出现多个变量同时共用一个value的情况,在执行完以后直接将各变量及value结构free掉。

这种方式是可行的,而且内存管理也很简单,但是,硬拷贝带来的一个问题是效率低,比如我们定义了一个变量然后赋值给另外一个变量,可能后面都只是只读操作,假如硬拷贝的话就会有多余的一份数据,这个问题的解决方案是:引用计数+写时复制。PHP变量的管理正是基于这两点实现的。

引用计数

引用计数是指在value中增加一个字段refcount记录指向当前value的数量,变量复制、函数传参时并不直接硬拷贝一份value数据,而是将refcount++,变量销毁时将refcount--,等到refcount减为0时表示已经没有变量引用这个value,将它销毁即可。

引用计数的信息位于给具体value结构的gc中:

从上面的zend_value结构可以看出并不是所有的数据类型都会用到引用计数,longdouble直接都是硬拷贝,只有value是指针的那几种类型才可能会用到引用计数。

下面再看一个例子:

$a = "hi~";$b = $a;

猜测一下变量$a/$b的引用情况。

这个不跟上面的例子一样吗?字符串"hi~"$a/$b两个引用,所以zend_string1(refcount=2)。但是这是错的,gdb调试发现上面例子zend_string的引用计数为0。这是为什么呢?

$a,$b -> zend_string_1(refcount=0,val="hi~")

事实上并不是所有的PHP变量都会用到引用计数,标量:true/false/double/long/null是硬拷贝自然不需要这种机制,但是除了这几个还有两个特殊的类型也不会用到:interned string(内部字符串,就是上面提到的字符串flag:IS_STR_INTERNED)、immutable array,它们的type是IS_STRINGIS_ARRAY,与普通string、array类型相同,那怎么区分一个value是否支持引用计数呢?还记得zval.u1中那个类型掩码type_flag吗?正是通过这个字段标识的,这个字段除了标识value是否支持引用计数外还有其它几个标识位,按位分割,注意:type_flagzval.value->gc.u.flag不是一个值。

支持引用计数的value类型其zval.u1.type_flag包含(注意是&,不是等于)IS_TYPE_REFCOUNTED

#define IS_TYPE_REFCOUNTED          (1<p style="color:rgb(51,51,51);clear:both;min-height:1em;font-family:'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;font-size:16px;">下面具体列下哪些类型会有这个标识:</p><pre style="color:rgb(62,62,62);font-size:18px;line-height:28.8px;" class="brush:php;toolbar:false;">|     type       | refcounted |
+----------------+------------+
|simple types    |            |
|string          |      Y     |
|interned string |            |
|array           |      Y     |
|immutable array |            |
|object          |      Y     |
|resource        |      Y     |
|reference       |      Y     |

simple types很显然用不到,不再解释,string、array、object、resource、reference有引用计数机制也很容易理解,下面具体解释下另外两个特殊的类型:

  • interned string:内部字符串,这是种什么类型?我们在PHP中写的所有字符都可以认为是这种类型,比如function name、class name、variable name、静态字符串等等,我们这样定义:$a = "hi~;"后面的字符串内容是唯一不变的,这些字符串等同于C语言中定义在静态变量区的字符串:char *a = "hi~";,这些字符串的生命周期为request期间,request完成后会统一销毁释放,自然也就无需在运行期间通过引用计数管理内存。

  • immutable array:只有在用opcache的时候才会用到这种类型,不清楚具体实现,暂时忽略。


写时复制

上一小节介绍了引用计数,多个变量可能指向同一个value,然后通过refcount统计引用数,这时候如果其中一个变量试图更改value的内容则会重新拷贝一份value修改,同时断开旧的指向,写时复制的机制在计算机系统中有非常广的应用,它只有在必要的时候(写)才会发生硬拷贝,可以很好的提高效率,下面从示例看下:

$a = array(1,2);$b = &$a;$c = $a;//发生分离$b[] = 3;


最终的结果:

不是所有类型都可以copy的,比如对象、资源,实时上只有string、array两种支持,与引用计数相同,也是通过zval.u1.type_flag标识value是否可复制的:

#define IS_TYPE_COLLECTABLE         (1<pre style="color:rgb(62,62,62);font-size:18px;line-height:28.8px;" class="brush:php;toolbar:false;">|     type       |  copyable  |
+----------------+------------+
|simple types    |            |
|string          |      Y     |
|interned string |            |
|array           |      Y     |
|immutable array |            |
|object          |            |
|resource        |            |
|reference       |            |

copyable的意思是当value发生duplication时是否需要copy,这个具体有两种情形下会发生:

  • a.从literal变量区复制到局部变量区,比如:$a = [];实际会有两个数组,而$a = "hi~";//interned string则只有一个string

  • b.局部变量区分离时(写时复制):如改变变量内容时引用计数大于1则需要分离,$a = [];$b = $a; $b[] = 1;这里会分离,类型是array所以可以复制,如果是对象:$a = new user;$b = $a;$a->name = "dd";这种情况是不会复制object的,$a、$b指向的对象还是同一个

具体literal、局部变量区变量的初始化、赋值后面编译、执行两篇文章会具体分析,这里知道变量有个copyable的属性就行了。

变量回收

PHP变量的回收主要有两种:主动销毁、自动销毁。主动销毁指的就是unset,而自动销毁就是PHP的自动管理机制,在return时减掉局部变量的refcount,即使没有显式的return,PHP也会自动给加上这个操作。

垃圾回收

PHP变量的回收是根据refcount实现的,当unset、return时会将变量的引用计数减掉,如果refcount减到0则直接释放value,这是变量的简单gc过程,但是实际过程中出现gc无法回收导致内存泄漏的bug,先看下一个例子:

$a = [1];$a[] = &$a;unset($a);

unset($a)之前引用关系:

unset($a)之后:

可以看到,unset($a)之后由于数组中有子元素指向$a,所以refcount > 0,无法通过简单的gc机制回收,这种变量就是垃圾,垃圾回收器要处理的就是这种情况,目前垃圾只会出现在array、object两种类型中,所以只会针对这两种情况作特殊处理:当销毁一个变量时,如果发现减掉refcount后仍然大于0,且类型是IS_ARRAY、IS_OBJECT则将此value放入gc可能垃圾双向链表中,等这个链表达到一定数量后启动检查程序将所有变量检查一遍,如果确定是垃圾则销毁释放。

标识变量是否需要回收也是通过u1.type_flag区分的:

#define IS_TYPE_COLLECTABLE
|     type       | collectable |
+----------------+-------------+
|simple types    |             |
|string          |             |
|interned string |             |
|array           |      Y      |
|immutable array |             |
|object          |      Y      |
|resource        |             |
|reference       |             |

具体的垃圾回收过程这里不再介绍。

以上是解析PHP7內核之變數的內部實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:CSDN。如有侵權,請聯絡admin@php.cn刪除

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
1 個月前By尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境