在引擎內部,乙個php的變數是儲存在「zval」結構中,此結構包含了變數的型別和值資訊,這個在之前的文章 變數的內部儲存:值和型別 中已經介紹了,此結構還有另外兩個字段資訊,乙個是」is_ref」(此字段在5.3.2版本中是is_ref__gc),此字段是乙個布林值,用來標識變數是否是乙個引用,通過這個字段,php引擎能夠區分一般的變數和引用變數。php**中可以通過 & 操作符號來建立乙個引用變數,建立的引用變數內部的zval的is_ref欄位就為1。zval中還有另外乙個欄位refcount(此字段在5.3.2版本中是refcount__gc),這個欄位是乙個計數器,表示有多少個變數名指向這個zval容器,當此字段為0時,表示沒有任何變數指向這個zval,那麼zval就可以被釋放,這是引擎內部對記憶體的一種優化。考慮如下**:
**中有兩個變變數a和
b,通過普通賦值方式將a賦
給 b,這樣b的
值和a相等,對b的
修改不會
對 a造成任何影響,那麼在這段**中,如果a和
b對應兩個不同的zval,那麼顯然是對記憶體的一種浪費,php的開發者也不會讓這樣的事情發生。所以實際上a和
b是指向同乙個zval。這個zval的型別是string,值是」hello world」,有a和
b兩個變數指向它,所以它的refcount=2, 由於是乙個普通賦值,所以is_ref欄位為0。 這樣就節省了記憶體開銷。
當執行a="h
ello
worl
d"之後
, a對應的zval的資訊為:a: (refcount=1, is_ref=0)=」hello world」
但執行b
= a之後,$a對應的zval的資訊為:a: (refcount=2, is_ref=0)=」hello world」
下面將之前的**修改一下:
<?php
$a = "hello world";
$b = &$a;
?>
這樣就通過引用賦值方式將a賦
給 b。
當執行a="h
ello
worl
d"之後
, a對應的zval的資訊為:a: (refcount=1, is_ref=0)=」hello world」
但執行b=&
a之後,$a對應的zval的資訊為:a: (refcount=2, is_ref=1)=」hello world」
可以發現is_ref欄位被設定成1了,這樣a和
b對應的zval就是乙個引用。這樣我們基本對引擎中變數的引用和計數有了乙個基本的了解,下面將介紹變數的分離。
考慮前面第一段**,用普通方式將a賦
給 b,在內部兩個變數還是指向同乙個zval的,這個時候如果我們將b的
值修改為
"new
stri
ng",
a變數的值依然是」hello world」:
<?php
$a = "hello world";
$b = $a;
$b = "new string";
echo $a;
echo $b;
?>
a和
b明明是指向同乙個zval,為什麼修改了b,
a還能保持不變呢,這就是copy on write(寫時複製)技術,簡單的說,當重新給b賦
值的時候
,會將 b從之前的zval中分離出來。分離之後,a和
b分別是指向不同的zval了。
寫時複製技術的乙個比較有名的應用是在unix類作業系統核心中,當乙個程序呼叫fork函式生成乙個子程序的時候,父子程序擁有相同的位址空間內容,在老版本的系統中,子程序是在fork的時候就將父程序的位址空間中的內容都拷貝乙份,對於規模較大的程式這個過程可能會有著很大的開銷,更崩潰的是,很多程序在fork之後,直接在子程序中呼叫exec執行另外乙個程式,這樣原來花了大量時間從父程序複製的位址空間都還沒來得及碰一下就被新的程序位址空間代替,這顯然是對資源的極大浪費,所以在後來的系統中,就使用了寫時複製技術,fork之後,子程序的位址空間還是簡單的指向父程序的位址空間,只有當子程序需要寫位址空間中的內容的時候,才會單獨分離乙份(一般以記憶體頁為單位)給子程序,這樣就算子程序馬上呼叫exec函式也沒關係,因為根本就不需要從父程序的位址空間中拷貝內容,這樣節約了記憶體同時又提高了速度。當b
從 a指向的zval分離出來之後,zval的refcount就要減1,這樣由之前的2變成了1,表示這個zval還有乙個變數指向它,就是a。
b變數指向了乙個新的zval,新的zval的refcount為1,值為字串」new string」,大概過程如下:
$a = "hello world" //a: (refcount=1, is_ref=0)="hello world"
$b = $a //a,b: (refcount=2, is_ref=0)="hello world"
$b = "new string" //a: (refcount=1, is_ref=0)="hello world" b: (refcount=1, is_ref=0)="new string"(發生分離操作)
這個分離邏輯可以表敘為:對乙個一般變數a(isref=0)進行一般賦值操作,如果a所指向的zval的計數refcount大於1,那麼需要為a重新分配乙個新的zval,並且把之前的zval的計數refcount減少1。
以上為普通賦值的情況,如果是引用賦值,我們看看這個變化過程:
$a = "hello world" //a: (refcount=1, is_ref=0)="hello world"
$b = &$a //a,b: (refcount=2, is_ref=1)="hello world"
$b = "new string" //a,b: (refcount=2, is_ref=1)="new string"
可以看出來,對乙個引用型別的zval進行賦值是不會進行分離操作的,實際上我們再產生乙個引用變數的時候是可能出現乙個分離操作的,只是時機有些不同:
在普通賦值的情況下,分離操作發生在$b=」new string」這一步,也就是在對變數賦新的值的時候,才會進行zval分離操作
在引用賦值的情況下,分離操作有可能發生在
b = &
a這一步,也就是在生成引用變數的時候
情況1就不多解釋了,情況2中強調是有可能發生分離,以前面的這**為例子,是否進行分離與a當
前指向的
zval
的ref
coun
t有關係
,**中
b = &a的
時候, a指向的zval的refcount=1,這個時候不需要進行分離操作,但是如果refcount=2,那麼就需要分離乙個zval出來。比如如下**:
<?php
$a = "hello world";
$c = $a;
$b = &$a;
$b = "new string";
?>
在執行引用賦值的時候,a指
向的zv
al的r
efco
unt=
2,因為
a和c同
時指向了
這個zv
al,所
以在b=&a的
時候,就
需要進行
乙個分離
操作,這
個分離操
作生成了
乙個re
f=1的
zval
,並且計
數為2,
因為a,b兩
個變數指
向分離出
來的zv
al,原
來的zv
al的r
efco
unt減
少1,所
以最終只
有 c指向乙個值為」hello world」,ref=0的zval1, a和
b指向乙個值為」hello world」,ref=1的zval2。 這樣我們對c的
修改時在
操作zv
al1,
對 a和$b的修改都是在操作zval2,這樣就符合引用的特性了。
此過程大致如下:
$a = "hello world"; //a: (refcount=1, is_ref=0)="hello world"
$c = $a; // a,c: (refcount=2, is_ref=0)="hello world"
$b = &$a; // c: (refcount=1, is_ref=0)="hello world" a,b: (refcount=2, is_ref=1)="hello world" (發生分離操作)
$b = "new string"; // c: (refcount=1, is_ref=0)="hello world" a,b: (refcount=2, is_ref=1)="new string"
試想一下如果不進行這個分離會有什麼後果?如果不進行分離,a,
b,c都
指向了同
乙個zv
al,對
b的修改也會影響到$c,這顯然是不符合php語言特性的。
這個分離邏輯可以表述為:將乙個一般變數a(isref=0)的引用賦給另外乙個變數b的時候,如果a的refcount大於1,那麼需要對a進行一次分離操作,分離之後的zval的isref等於1,refcount等於2
通過以上的一些知識和分離邏輯讀者應該可以很容易分析其它的一些情況。比如將乙個引用變數a(isref=1)的引用賦給一般變數b的時候,需要將b之前指向的zval的refcount減少1,然後將b指向a的zval,a的zval的refcount加1,沒有任何分離操作
這些理論結合實際**會讓你更容易理解這個過程。
手工引用計數中規則
使用設值方法為屬性賦值時 assign retain copy三個特性的實現 self.property newvalue assign的特性會是這樣 property newvalue retain特性會是這樣 if property 0 property release property new...
引用變數與物件
各種oo語言中採用不同方式例項化 建立 物件。在c 中,當宣告乙個使用者自定義型別變數,如 student y 的時候,並沒有在記憶體中真的建立物件,而只是宣告了studnet型別的引用變數y。該引用變數可能會指向乙個student物件,但目前還沒有 或者說,它的值為null,null是c 關鍵字,...
C 引用變數與數值變數的區別
1.值引數 當利用值向方法傳遞引數時,編譯程式給實參的值做乙份拷貝,並且將此拷貝傳遞給該方法。被呼叫的方法不傳經修改記憶體中實參的值,所以使用值引數時,可以保證實際值是安全的。在呼叫方法時,如果形式化引數的型別是值引數的話,呼叫的實參的值必須保證是正確的值表示式。在下面的例子中,程式設計師並沒有實現...