溫馨提示×

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

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

PHP中變量的實(shí)現(xiàn)方法

發(fā)布時(shí)間:2020-06-28 15:54:35 來(lái)源:億速云 閱讀:155 作者:元一 欄目:編程語(yǔ)言

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)PHP中變量的實(shí)現(xiàn)方法,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

變量是用于存儲(chǔ)信息的"容器":

與代數(shù)類似,可以給 PHP 變量賦予某個(gè)值(x=5)或者表達(dá)式(z=x+y)。

變量可以是很短的名稱(如 x 和 y)或者更具描述性的名稱(如 age、carname、totalvolume)。

PHP 變量規(guī)則:

undefined
undefined
undefined
undefined
undefined

PHP變量實(shí)現(xiàn)的基礎(chǔ)結(jié)構(gòu)是zval,各種類型的實(shí)現(xiàn)均基于此結(jié)構(gòu)實(shí)現(xiàn),是PHP中最基礎(chǔ)的一個(gè)結(jié)構(gòu),每個(gè)PHP變量都對(duì)應(yīng)一個(gè)zval,下面就看下這個(gè)結(jié)構(gòu)以及PHP變量的內(nèi)存管理機(jī)制。

zval結(jié)構(gòu)

PHP中變量的實(shí)現(xiàn)方法

zval結(jié)構(gòu)比較簡(jiǎn)單,內(nèi)嵌一個(gè)union類型的zend_value保存具體變量類型的值或指針,zval中還有兩個(gè)union:u1、u2:
  • u1:它的意義比較直觀,變量的類型就通過u1.type區(qū)分,另外一個(gè)值type_flags為類型掩碼,在變量的內(nèi)存管理、gc機(jī)制中會(huì)用到,第三部分會(huì)詳細(xì)分析,至于后面兩個(gè)const_flagsreserved暫且不管

  • u2:這個(gè)值純粹是個(gè)輔助值,假如zval只有:value、u1兩個(gè)值,整個(gè)zval的大小也會(huì)對(duì)齊到16byte,既然不管有沒有u2大小都是16byte,把多余的4byte拿出來(lái)用于一些特殊用途還是很劃算的,比如next在哈希表解決哈希沖突時(shí)會(huì)用到,還有fe_pos在foreach會(huì)用到......

zend_value可以看出,除longdouble類型直接存儲(chǔ)值外,其它類型都為指針,指向各自的結(jié)構(gòu)。

類型

zval.u1.type類型:

PHP中變量的實(shí)現(xiàn)方法

標(biāo)量類型

最簡(jiǎn)單的類型是true、false、long、double、null,其中true、false、null沒有value,直接根據(jù)type區(qū)分,而long、double的值則直接存在value中:zend_long、double,也就是標(biāo)量類型不需要額外的value指針。

字符串

PHP中字符串通過zend_string表示:

PHP中變量的實(shí)現(xiàn)方法

  • gc:變量引用信息,比如當(dāng)前value的引用數(shù),所有用到引用計(jì)數(shù)的變量類型都會(huì)有這個(gè)結(jié)構(gòu),3.1節(jié)會(huì)詳細(xì)分析

  • h:哈希值,數(shù)組中計(jì)算索引時(shí)會(huì)用到

  • len:字符串長(zhǎng)度,通過這個(gè)值保證二進(jìn)制安全

  • val:字符串內(nèi)容,變長(zhǎng)struct,分配時(shí)按len長(zhǎng)度申請(qǐng)內(nèi)存

事實(shí)上字符串又可具體分為幾類:IS_STR_PERSISTENT(通過malloc分配的)、IS_STR_INTERNED(php代碼里寫的一些字面量,比如函數(shù)名、變量值)、IS_STR_PERMANENT(永久值,生命周期大于request)、IS_STR_CONSTANT(常量)、IS_STR_CONSTANT_UNQUALIFIED,這個(gè)信息通過flag保存:zval.value->gc.u.flags,后面用到的時(shí)候再具體分析。

數(shù)組

array是PHP中非常強(qiáng)大的一個(gè)數(shù)據(jù)結(jié)構(gòu),它的底層實(shí)現(xiàn)就是普通的有序HashTable,這里簡(jiǎn)單看下它的結(jié)構(gòu),下一節(jié)會(huì)單獨(dú)分析數(shù)組的實(shí)現(xiàn)。

PHP中變量的實(shí)現(xiàn)方法

對(duì)象/資源

PHP中變量的實(shí)現(xiàn)方法

對(duì)象比較常見,資源指的是tcp連接、文件句柄等等類型,這種類型比較靈活,可以隨意定義struct,通過ptr指向,后面會(huì)單獨(dú)分析這種類型,這里不再多說(shuō)。

引用

引用是PHP中比較特殊的一種類型,它實(shí)際是指向另外一個(gè)PHP變量,對(duì)它的修改會(huì)直接改動(dòng)實(shí)際指向的zval,可以簡(jiǎn)單的理解為C中的指針,在PHP中通過&操作符產(chǎn)生一個(gè)引用變量,也就是說(shuō)不管以前的類型是什么,&首先會(huì)將新生成一個(gè)zval,類型為IS_REFERENCE,然后將val的value指向原來(lái)zval的value。

PHP中變量的實(shí)現(xiàn)方法

結(jié)構(gòu)非常簡(jiǎn)單,除了公共部分zend_refcounted_h外只有一個(gè)val,舉個(gè)示例看下具體的結(jié)構(gòu)關(guān)系:

PHP中變量的實(shí)現(xiàn)方法

最終的結(jié)果如圖:

PHP中變量的實(shí)現(xiàn)方法

注意:引用只能通過&產(chǎn)生,無(wú)法通過賦值傳遞,比如:

PHP中變量的實(shí)現(xiàn)方法

$b = &$a這時(shí)候$a$b的類型是引用,但是$c = $b并不會(huì)直接將$b賦值給$c,而是把$b實(shí)際指向的zval賦值給$c,如果想要$c也是一個(gè)引用則需要這么操作:

PHP中變量的實(shí)現(xiàn)方法

這個(gè)也表示PHP中的引用只可能有一層,不會(huì)出現(xiàn)一個(gè)引用指向另外一個(gè)引用的情況,也就是沒有C語(yǔ)言中指針的指針的概念。

內(nèi)存管理

接下來(lái)分析下變量的分配、銷毀。

在分析變量?jī)?nèi)存管理之前我們先自己想一下可能的實(shí)現(xiàn)方案,最簡(jiǎn)單的處理方式:定義變量時(shí)alloc一個(gè)zval及對(duì)應(yīng)的value結(jié)構(gòu)(ref/arr/str/res...),賦值、函數(shù)傳參時(shí)硬拷貝一個(gè)副本,這樣各變量最終的值完全都是獨(dú)立的,不會(huì)出現(xiàn)多個(gè)變量同時(shí)共用一個(gè)value的情況,在執(zhí)行完以后直接將各變量及value結(jié)構(gòu)free掉。

這種方式是可行的,而且內(nèi)存管理也很簡(jiǎn)單,但是,硬拷貝帶來(lái)的一個(gè)問題是效率低,比如我們定義了一個(gè)變量然后賦值給另外一個(gè)變量,可能后面都只是只讀操作,假如硬拷貝的話就會(huì)有多余的一份數(shù)據(jù),這個(gè)問題的解決方案是:引用計(jì)數(shù)+寫時(shí)復(fù)制。PHP變量的管理正是基于這兩點(diǎn)實(shí)現(xiàn)的。

引用計(jì)數(shù)

引用計(jì)數(shù)是指在value中增加一個(gè)字段refcount記錄指向當(dāng)前value的數(shù)量,變量復(fù)制、函數(shù)傳參時(shí)并不直接硬拷貝一份value數(shù)據(jù),而是將refcount++,變量銷毀時(shí)將refcount--,等到refcount減為0時(shí)表示已經(jīng)沒有變量引用這個(gè)value,將它銷毀即可。

PHP中變量的實(shí)現(xiàn)方法

引用計(jì)數(shù)的信息位于給具體value結(jié)構(gòu)的gc中:

PHP中變量的實(shí)現(xiàn)方法

從上面的zend_value結(jié)構(gòu)可以看出并不是所有的數(shù)據(jù)類型都會(huì)用到引用計(jì)數(shù),long、double直接都是硬拷貝,只有value是指針的那幾種類型才可能會(huì)用到引用計(jì)數(shù)。

下面再看一個(gè)例子:

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

猜測(cè)一下變量$a/$b的引用情況。

這個(gè)不跟上面的例子一樣嗎?字符串"hi~"$a/$b兩個(gè)引用,所以zend_string1(refcount=2)。但是這是錯(cuò)的,gdb調(diào)試發(fā)現(xiàn)上面例子zend_string的引用計(jì)數(shù)為0。這是為什么呢?

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

事實(shí)上并不是所有的PHP變量都會(huì)用到引用計(jì)數(shù),標(biāo)量:true/false/double/long/null是硬拷貝自然不需要這種機(jī)制,但是除了這幾個(gè)還有兩個(gè)特殊的類型也不會(huì)用到:interned string(內(nèi)部字符串,就是上面提到的字符串flag:IS_STR_INTERNED)、immutable array,它們的type是IS_STRING、IS_ARRAY,與普通string、array類型相同,那怎么區(qū)分一個(gè)value是否支持引用計(jì)數(shù)呢?還記得zval.u1中那個(gè)類型掩碼type_flag嗎?正是通過這個(gè)字段標(biāo)識(shí)的,這個(gè)字段除了標(biāo)識(shí)value是否支持引用計(jì)數(shù)外還有其它幾個(gè)標(biāo)識(shí)位,按位分割,注意:type_flagzval.value->gc.u.flag不是一個(gè)值。

支持引用計(jì)數(shù)的value類型其zval.u1.type_flag包含(注意是&,不是等于)IS_TYPE_REFCOUNTED

#define IS_TYPE_REFCOUNTED          (1<<2)

下面具體列下哪些類型會(huì)有這個(gè)標(biāo)識(shí):

|     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有引用計(jì)數(shù)機(jī)制也很容易理解,下面具體解釋下另外兩個(gè)特殊的類型:

  • interned string:內(nèi)部字符串,這是種什么類型?我們?cè)赑HP中寫的所有字符都可以認(rèn)為是這種類型,比如function name、class name、variable name、靜態(tài)字符串等等,我們這樣定義:$a = "hi~;"后面的字符串內(nèi)容是唯一不變的,這些字符串等同于C語(yǔ)言中定義在靜態(tài)變量區(qū)的字符串:char *a = "hi~";,這些字符串的生命周期為request期間,request完成后會(huì)統(tǒng)一銷毀釋放,自然也就無(wú)需在運(yùn)行期間通過引用計(jì)數(shù)管理內(nèi)存。

  • immutable array:只有在用opcache的時(shí)候才會(huì)用到這種類型,不清楚具體實(shí)現(xiàn),暫時(shí)忽略。

寫時(shí)復(fù)制

上一小節(jié)介紹了引用計(jì)數(shù),多個(gè)變量可能指向同一個(gè)value,然后通過refcount統(tǒng)計(jì)引用數(shù),這時(shí)候如果其中一個(gè)變量試圖更改value的內(nèi)容則會(huì)重新拷貝一份value修改,同時(shí)斷開舊的指向,寫時(shí)復(fù)制的機(jī)制在計(jì)算機(jī)系統(tǒng)中有非常廣的應(yīng)用,它只有在必要的時(shí)候(寫)才會(huì)發(fā)生硬拷貝,可以很好的提高效率,下面從示例看下:

$a = array(1,2);$b = &$a;$c = $a;//發(fā)生分離$b[] = 3;

最終的結(jié)果:

PHP中變量的實(shí)現(xiàn)方法

不是所有類型都可以copy的,比如對(duì)象、資源,實(shí)時(shí)上只有string、array兩種支持,與引用計(jì)數(shù)相同,也是通過zval.u1.type_flag標(biāo)識(shí)value是否可復(fù)制的:

#define IS_TYPE_COLLECTABLE         (1<<3)
|     type       |  copyable  |
+----------------+------------+
|simple types    |            |
|string          |      Y     |
|interned string |            |
|array           |      Y     |
|immutable array |            |
|object          |            |
|resource        |            |
|reference       |            |

copyable的意思是當(dāng)value發(fā)生duplication時(shí)是否需要copy,這個(gè)具體有兩種情形下會(huì)發(fā)生:

  • a.從literal變量區(qū)復(fù)制到局部變量區(qū),比如:$a = [];實(shí)際會(huì)有兩個(gè)數(shù)組,而$a = "hi~";//interned string則只有一個(gè)string

  • b.局部變量區(qū)分離時(shí)(寫時(shí)復(fù)制):如改變變量?jī)?nèi)容時(shí)引用計(jì)數(shù)大于1則需要分離,$a = [];$b = $a; $b[] = 1;這里會(huì)分離,類型是array所以可以復(fù)制,如果是對(duì)象:$a = new user;$b = $a;$a->name = "dd";這種情況是不會(huì)復(fù)制object的,$a、$b指向的對(duì)象還是同一個(gè)

具體literal、局部變量區(qū)變量的初始化、賦值后面編譯、執(zhí)行兩篇文章會(huì)具體分析,這里知道變量有個(gè)copyable的屬性就行了。

變量回收

PHP變量的回收主要有兩種:主動(dòng)銷毀、自動(dòng)銷毀。主動(dòng)銷毀指的就是unset,而自動(dòng)銷毀就是PHP的自動(dòng)管理機(jī)制,在return時(shí)減掉局部變量的refcount,即使沒有顯式的return,PHP也會(huì)自動(dòng)給加上這個(gè)操作。

垃圾回收

PHP變量的回收是根據(jù)refcount實(shí)現(xiàn)的,當(dāng)unset、return時(shí)會(huì)將變量的引用計(jì)數(shù)減掉,如果refcount減到0則直接釋放value,這是變量的簡(jiǎn)單gc過程,但是實(shí)際過程中出現(xiàn)gc無(wú)法回收導(dǎo)致內(nèi)存泄漏的bug,先看下一個(gè)例子:

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

unset($a)之前引用關(guān)系:

PHP中變量的實(shí)現(xiàn)方法

unset($a)之后:

PHP中變量的實(shí)現(xiàn)方法

可以看到,unset($a)之后由于數(shù)組中有子元素指向$a,所以refcount > 0,無(wú)法通過簡(jiǎn)單的gc機(jī)制回收,這種變量就是垃圾,垃圾回收器要處理的就是這種情況,目前垃圾只會(huì)出現(xiàn)在array、object兩種類型中,所以只會(huì)針對(duì)這兩種情況作特殊處理:當(dāng)銷毀一個(gè)變量時(shí),如果發(fā)現(xiàn)減掉refcount后仍然大于0,且類型是IS_ARRAY、IS_OBJECT則將此value放入gc可能垃圾雙向鏈表中,等這個(gè)鏈表達(dá)到一定數(shù)量后啟動(dòng)檢查程序?qū)⑺凶兞繖z查一遍,如果確定是垃圾則銷毀釋放。

標(biāo)識(shí)變量是否需要回收也是通過u1.type_flag區(qū)分的:

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

具體的垃圾回收過程這里不再介紹。

上述就是小編為大家分享的PHP中變量的實(shí)現(xiàn)方法了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

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

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎ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