多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內(nèi)最全IT社區(qū)平臺 聯(lián)系我們 | 收藏本站
阿里云優(yōu)惠2
您當(dāng)前位置:首頁 > php開源 > php教程 > [置頂] PHP內(nèi)核探索之變量(2)-理解引用

[置頂] PHP內(nèi)核探索之變量(2)-理解引用

來源:程序員人生   發(fā)布時間:2014-12-13 09:00:13 閱讀次數(shù):4176次

本文主要內(nèi)容:

  1. 引論
  2. 符號表與zval
  3. 援用原理
  4. 回到最初的問題

1、引論

  很久之前寫了1篇關(guān)于援用的文章,當(dāng)時寫的寥寥草草,很多原理都沒有說清楚。最近在翻閱Derick Rethans(home: http://derickrethans.nl/ Github: https://github.com/derickr)大牛之前做的報告時,發(fā)現(xiàn)了1篇講授PHP援用機制的文章,也就是這個PDF.文中從zval和符號表的角度講授了援用計數(shù)、援用傳參、援用返回、全局參數(shù)等的原理,洋洋灑灑,圖文并茂,甚是精彩,建議童鞋們有時間都讀讀原版,相信會有很多的收獲。

  空話不多說,接著說今天的正題。

  我們知道,很多語言都提供了援用的機制,援用可讓我們使用不同的名字(或符號)訪問一樣的內(nèi)容。PHP手冊中對援用的定義是:"在PHP中援用意味著用不同的名字訪問同1個變量內(nèi)容。這其實不像C的指針,替換的是,援用是符號表別名。",換句話說,援用實現(xiàn)了某種情勢的"綁定"。例如我們常常碰到的這類面試題,便是援用的典范:

$a = array(1,2,3,4); foreach($a as &$v){ $v *= $v; } foreach($a as $v){ echo $v; }

  拋開本題的輸出不談,我們今天就跟隨Derick Rethans先輩的腳步,1步1步去揭開援用的神秘面紗。

2、  符號表和zval

  在開始援用的原理之前,我們有必要對文中反復(fù)出現(xiàn)的術(shù)語做個簡單的說明,其中最主要也最重要的便是: 1.符號表 2.zval.

1.   符號表

  計算機語言是人與機器交換的工具,但不幸的是,我們賴以生存和引以為傲的高級語言卻沒法直接在計算機上履行,由于計算機只能理解某種情勢的機器語言。這意味著,高級語言必須要經(jīng)過編譯(或解釋)進程才能被計算機理解和履行。在這其間,要經(jīng)過詞法分析、語法分析、語義分析、中間代碼生成和優(yōu)化等很多復(fù)雜的進程,而這些進程中,編譯程序可能要反復(fù)用到源程序中出現(xiàn)的標(biāo)識符等信息(例如變量的類型檢查、語義分析階段的語義檢查),這些信息便是保存在不同的符號表中的。符號表保存了源程序中標(biāo)識符的名字和屬性信息,這些信息可能包括:類型、存儲類型、作用域、存儲分配信息和其他1些額外信息等。為了高效的插入和查詢符號表項,很多編譯器的符號表都使用Hashtable來實現(xiàn)。我們可以簡單的理解為:符號表就是1個保存了符號名和該符號的各類屬性的hashtable或map。例如,對程序:

$str = 'this is a test'; function foo( $a, $b ){ $tmp = 12; return $tmp + $a + $b; } function to(){ }

1個可能的符號表(并不是實際的符號表)是類似這樣的結(jié)構(gòu):

 

  我們其實不去關(guān)注符號表的具體結(jié)構(gòu),只需要知道:每一個函數(shù)、類、命名空間等都有自己的獨立的符號表(與全局的符號表分開)。說到這里,突然想起來1件事情,最開始使用PHP編程的時候,在讀extract()函數(shù)的手冊時,對"從數(shù)組中將變量導(dǎo)入到當(dāng)前的符號表"這句話的含義百思不得其解,更是對先輩們所說的"不建議使用extract($_POST)和extract($_GET)提取變量"的建議萬分苦惱。實際上,extract的濫用不但會有嚴(yán)重的安全性問題,而且會污染當(dāng)前的符號表( active symbol table)。

  那末active symbol table又是甚么東西呢?

  我們知道,PHP代碼的履行進程中,幾近都是從全局作用域開始,順次掃描,順序履行。如果遇到函數(shù)調(diào)用,則進入該函數(shù)的內(nèi)部履行,該函數(shù)履行終了以后會返回到調(diào)用程序繼續(xù)履行。這意味著,必須要有某種機制用于辨別不同階段所要使用的符號表,否則就會造成編譯和履行的錯亂。Active symbol table便是用于標(biāo)志當(dāng)前活動的符號表(這時候應(yīng)當(dāng)最少存在著全局的global symbol table和活動的active symbol table,通常情況下,active symbol table就是指global symbol table)。符號表其實不是1開始就建立好的,而是隨著編譯程序的掃描不斷添加和更新的。在進入函數(shù)調(diào)用時,zend(PHP的語言解釋引擎)會創(chuàng)建該函數(shù)的符號表,并將active symbol table指向該符號表。也就是說,在任意時刻使用的的符號表都應(yīng)當(dāng)是當(dāng)前的active symbol table。

  以上就是符號表的全部內(nèi)容了,我們簡單抽離1下其中的關(guān)鍵內(nèi)容:

  1. 符號表記錄了程序中符號的name-attribute對,這些信息對編譯和履行是相當(dāng)重要的。
  2. 符號表類似1個map或hashtable
  3. 符號表不是1開始就建立好的,而是不斷添加和更新的進程。
  4. 活動符號表是1個指針,指向的是當(dāng)前活動的符號表。

  更多的資料可以查看:

  1. http://www.scs.stanford.edu/11wi-cs140/pintos/specs/sysv-abi-update.html/ch4.symtab.html

  2. http://arantxa.ii.uam.es/~modonnel/Compilers/04_SymbolTablesI.pdf

2.       Zval

  在上1篇博客(PHP內(nèi)核探索之變量(1)Zval)中,我們已對zval的結(jié)構(gòu)和基本原理有了1些了解。對zval不了解的童鞋可以先看看。為了方便瀏覽,我們再次貼出zval的結(jié)構(gòu):


struct _zval_struct {
    zvalue_value value;       /* value */
    zend_uint refcount__gc;   /* variable ref count */
    zend_uchar type;         /* active type */
    zend_uchar is_ref__gc;    /* if it is a ref variable */
};

typedef struct _zval_struct zval;

3、援用

1.  援用計數(shù)

  正如上節(jié)所言,zval是PHP變量底層的真正容器,為了節(jié)省空間,其實不是每一個變量都有自己獨立的zval容器,例如對賦值(assign-by-value)操作:$a = $b(假定$b,$a都不是援用型變量),Zend其實不會為$b變量開辟新的空間,而是將符號表中a符號和b符號指向同1個zval。只有在其中1個變量產(chǎn)生變化時,才會履行zval分離的操作。這被稱為COW(Copy-on-write)的機制,可以在1定程度上節(jié)省內(nèi)存和提高效力。

  為了實現(xiàn)上述機制,需要對zval的援用狀態(tài)做標(biāo)記,zval的結(jié)構(gòu)中,refcount__gc便是用于計數(shù)的,這個值記錄了有多少個變量指向該zval, 在上述賦值操作中,$a=$b ,會增加原始的$b的zval的refcount值。關(guān)于這1點,上次(PHP內(nèi)核探索之變量(1)Zval)已做了詳細的解釋,這里不再贅述。

2.         函數(shù)傳參

  在腳本履行的進程中,全局的符號表幾近是1直存在的,但除這個全局的global symbol table,實際上還會生成其他的symbol table:例如函數(shù)調(diào)用的進程中,Zend會創(chuàng)建該函數(shù)的內(nèi)部symbol table,用于寄存函數(shù)內(nèi)部變量的信息,而在函數(shù)調(diào)用結(jié)束后,會刪除該symbol table。我們接下來以1個簡單的函數(shù)調(diào)用為例,介紹1下在傳參的進程中,變量和zval的狀態(tài)變化,我們使用的測試腳本是:

function do_zval_test($s){ $s = "change "; return $s; } $a = "before"; $b = do_zval_test($a);

我們來逐漸分析:

(1).   $a = "before";

  這會為$a變量開辟1個新的zval(refcount=1,is_ref=0),以下所示:

  

(2).   函數(shù)調(diào)用do_zval_test($a)

  由于函數(shù)的調(diào)用,Zend會為do_zval_test這個函數(shù)創(chuàng)建單獨的符號表(其中包括該函數(shù)內(nèi)部的符號s),同時,由于$s實際上是函數(shù)的形參,因此其實不會為$s創(chuàng)建新的zval,而是指向$a的zval。這時候,$a指向的zval的refcount應(yīng)當(dāng)為3(分別是$a,$s和函數(shù)調(diào)用堆棧):

a: (refcount=3, is_ref=0)='before func'

  以下圖所示:

                    

(3).函數(shù)內(nèi)部履行$s = "change "

  由于$s的值產(chǎn)生了改變,因此會履行zval分離,為s專門copy生成1個新的zval:

 

(4).函數(shù)返回 return $s ; $b = do_zval_test($a).

  $b與$s同享zval(暫時),準(zhǔn)備燒毀函數(shù)中的符號表:

 

(5).   燒毀函數(shù)中的符號表,回到Global環(huán)境中:

 

  這里我們順便說1句,在你使用debug_zval_dump()等函數(shù)查看zval的refcount時,會令zval本身的refcount值加1,所以實際的refcount的值應(yīng)當(dāng)是打印出的refcount減1,以下所示:

$src = "string"; debug_zval_dump($src);

結(jié)果是:

string(6) "string" refcount(2)

3.         援用初探

同上,我們還是直接上代碼,然后1步步分析(這個例子比較簡單,為了完全性,我們還是略微分析1下):

$a = "simple test"; $b = &a; $c = &a; $b = 42; unset($c); unset($b);

則變量與zval的對應(yīng)關(guān)系以下圖所示:(因而可知,unset的作用僅僅是將變量從符號表中刪除,并減少對應(yīng)zval的refcount值)

 

上圖中值得注意的最后1步,在unset($b)以后,zval的is_ref值又變成了0。

那如果是混合了援用(assign-by-reference)和普通賦值(assign-by-value)的腳本,又是甚么情況呢?

我們的測試腳本:

(1). 先普通賦值后援用賦值

$a = "src"; $b = $a; $c = &$b;

具體的進程見下圖:

 

(2). 先援用賦值后普通賦值

$a = "src"; $b = &$a; $c = $a;

具體進程見下圖:

 

4.  傳遞援用

一樣,向函數(shù)傳遞的參數(shù)也能夠以援用的情勢傳遞,這樣可以在函數(shù)內(nèi)部修改變量的值。作為實例,我們?nèi)允褂?(函數(shù)傳參)中的腳本,只是參數(shù)改成援用的情勢:

function do_zval_test(&$s){ $s = "after"; return $s; } $a = "before"; $b = do_zval_test($a);

這與上述函數(shù)傳參進程基本1致,不同的是,援用的傳遞使得$a的值產(chǎn)生了變化。而且,在函數(shù)調(diào)用結(jié)束以后 $a的is_ref恢復(fù)成0:

 

可以看出,與普通的值傳遞相比,援用傳遞的不同在于:

(1)     第3步 $s = "change";時,并沒有為$s新建1個zval,而是與$a指向同1個zval,這個zval的is_ref=1。

(2)     還是第3步。$s = "change";履行后,由于zval的is_ref=1,因此,間接的改變了$a的值

5.  援用返回

  PHP支持的另外一個特性是援用返回。我們知道,在C/C++中,函數(shù)返回值時,實際上會生成1個值的副本,而在援用返回時,其實不會生成副本,這類援用返回的方式可以在1定程度上節(jié)省內(nèi)存和提高效力。而在PHP中,情況其實不完全是這樣。那末,究竟甚么是援用返回呢?PHP手冊上是這么說的:"援用返回用在當(dāng)想用函數(shù)找到援用應(yīng)當(dāng)被綁定在哪個變量上面時",是否是1頭霧水,完全不知所云?其實,英文手冊上是這樣描寫的"Returning by reference is useful when you want to use a function to find to which variable a reference should be bound"。提取文中的主干和關(guān)鍵點,我們可以得到這樣的信息:

(1).       援用返回是將援用綁定在1個變量上。

(2).       這個變量不是肯定的,而是通過函數(shù)得到的(否者我們就能夠使用普通的援用了)。

這其實也說明了援用返回的局限性:函數(shù)必須返回1個變量,而不能是1個表達式,否者就會出現(xiàn)類似下面的問題:

PHP Notice:  Only variable references should be returned by reference in xxx(參看PHP手冊中的Note).

那末,援用返回時如何工作的呢?例如,對以下的例子:

function &find_node($key,&$tree){ $item = &$tree[$key]; return $item; } $tree = array(1=>'one',2=>'two',3=>'three'); $node =& find_node(3,$tree); $node ='new';

Zend都做了哪些工作呢?我們1步步來看。

(1).    $tree = array(1=>'one',2=>'two',3=>'three')

同之前1樣,這會在Global symbol table中添加tree這個symbol,并生成該變量的zval。同時,為數(shù)組$tree的每一個元素都生成相應(yīng)的zval:

tree: (refcount=1, is_ref=0)=array (
    1 => (refcount=1, is_ref=0)='one',
    2 => (refcount=1, is_ref=0)='two',
    3 => (refcount=1, is_ref=0)='three'
)

以下圖所示:

(2). find_node(3,&$tree)

  由于函數(shù)調(diào)用,Zend會進入函數(shù)的內(nèi)部,創(chuàng)建該函數(shù)的內(nèi)部symbol table,同時,由于傳遞的參數(shù)是援用參數(shù),因此zval的is_ref被標(biāo)志為1,而refcount的值增加為3(分別是全局tree,內(nèi)部tree和函數(shù)堆棧):

 

(3)$item = &$tree[$key];

  由于item是$tree[$key]的援用(在本例的調(diào)用中,$key是3),因此更新$tree[$key]指向zval的is_ref和refcount值:

 

(4)return $item,并履行援用綁定:


(5)函數(shù)返回,燒毀局部符號表。

  tree對應(yīng)的zval的is_ref恢復(fù)了0,refcount=1,$tree[3]被綁定在了$node變量上,對該變量的任何改變都會間接更改$tree[3]:  

            

(6) 更改$node的值,會反射到$tree的節(jié)點上,$node ='new':

 

Note:為了使用援用返回,必須在函數(shù)定義和函數(shù)調(diào)用的地方都顯式的使用&符號。

6.    Global關(guān)鍵字

PHP中允許我們在函數(shù)內(nèi)部使用Global關(guān)鍵字援用全局變量(不加global關(guān)鍵字時援用的是函數(shù)的局部變量),例如:

$var = "outside"; function inside() { $var = "inside"; echo $var; global $var; echo $var; } inside();

輸出為insideoutside

我們只知道global關(guān)鍵字建立了1個局部變量和全局變量的綁定,那末具體機制是甚么呢?

使用以下的腳本測試:

$var = "one"; function update_var($value){ global $var; unset($var); global $var; $var = $value; } update_var('four'); echo $var;

具體的分析進程為:

(1).$var = 'one';

     同之前1樣,這會在全局的symbol table中添加var符號,并創(chuàng)建相應(yīng)的zval:

 

(2).update_var('four')

     由于直接傳遞的是string而不是變量,因此會創(chuàng)建1個zval,該zval的is_ref=0,ref_count=2(分別是形參$value和函數(shù)的堆棧),以下所示:

 

(3)global $var

  global $var這句話,實際上會履行兩件事情:

    (1).在函數(shù)內(nèi)部的符號表中插入局部的var符號

    (2).建立局部$var與全局變量$var之間的援用.

(4)unset($var);

     這里要注意的是,unset只是刪除函數(shù)內(nèi)部符號表中var符號,而不是刪除全局的。同時,更新原zval的refcount值和is_ref援用標(biāo)志(援用解綁):

 

(5).global $var

     同3,再次建立局部$var與全局的$var的援用:

 

(6)$var = $value;

  更改$var對應(yīng)的zval的值,由于援用的存在,全局的$var的值也隨之改變:

                  

(7)函數(shù)返回,燒毀局部符號表(又回到最初的出發(fā)點,但,1切已大不1樣了):

 

據(jù)此,我們可以總結(jié)出global關(guān)鍵字的進程和特性:

  1. 函數(shù)中聲明global,會在函數(shù)內(nèi)部生成1個局部的變量,并與全局的變量建立援用
  2. 函數(shù)中對global變量的任何更改操作都會間接更改全局變量的值。
  3. 函數(shù)unset局部變量不會影響global,而只是消除與全局變量的綁定。

4、回到最初的問題

現(xiàn)在,我們對援用已有了1個基本的認識。讓我們回到最初的問題:

$a = array(1,2,3); foreach($a as &$v){ $v *= $v; } foreach($a as $v){ echo $v; }

這當(dāng)中,究竟產(chǎn)生了甚么事情呢?

(1).$a = array(1,2,3);

這會在全局的symbol table中生成$a的zval并且為每一個元素也生成相應(yīng)的zval:

 

(2).   foreach($a as &$v) {$v *= $v;}

這里由因而援用綁定,所以相當(dāng)于對數(shù)組中的元素履行:

$v = &$a[0];
$v = &$a[1];
$v = &$a[2];

履行進程以下:

我們發(fā)現(xiàn),在這次的foreach履行終了以后,$v = &$a[2].

(3)第2次foreach循環(huán)

foreach($a as $v){ echo $v; }

這次由于是普通的assign-by-value的賦值情勢,因此,類似與履行:

$v = $a[0];
$v = $a[1];
$v = $a[2];

別忘了$v現(xiàn)在是$a[2]的援用,因此,賦值的進程會間接更改$a[2]的值。

進程以下:

因此,輸出結(jié)果應(yīng)當(dāng)為144.

附:本文中的zval的調(diào)試方法。

如果要查看某1進程中zval的變化,最好的辦法是在該進程的前后均加上調(diào)試代碼。例如

$a = 123; xdebug_debug_zval('a'); $b=&$a; xdebug_debug_zval('a');

配合畫圖,可以得到1個直觀的zval更新進程。

參考文獻:

  1. http://en.wikipedia.org/wiki/Symbol_table
  2. http://arantxa.ii.uam.es/~modonnel/Compilers/04_SymbolTablesI.pdf
  3. http://web.cs.wpi.edu/~kal/courses/cs4533/module5/myst.html
  4. http://www.cs.dartmouth.edu/~mckeeman/cs48/mxcom/doc/TypeInference.pdf
  5. http://www.cs.cornell.edu/courses/cs412/2008sp/lectures/lec12.pdf
  6. http://php.net/manual/zh/language.references.return.php
  7. http://stackoverflow.com/questions/10057671/how-foreach-actually-works

由于寫作匆忙,文中難免會有毛病的地方,歡迎指出探討。

生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對您的學(xué)習(xí)有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關(guān)閉
程序員人生
主站蜘蛛池模板: 国产精品久久久久久免费播放 | 乱人伦中文视频在线 | porn在线视频一区二区 | 精品精品国产高清a毛片牛牛 | 在线视频精品视频 | 亚洲天堂视频网站 | 国产片在线看 | 欧美性色欧美a在线播放 | 在线播放亚洲美女视频网站 | 亚洲小视频 | 在线免费午夜视频 | 国产在线观看成人免费视频 | 一区二区三区精品国产 | 中文字幕视频网 | 亚洲精品在线不卡 | jlzzjlzzjlzz亚洲女 | 中文字幕视频网 | 亚洲欧美在线视频免费 | 欧美精品18videos性欧美 | 五月婷婷在线观看视频 | a级毛片黄 | 久久77777| 另类综合小说 | 国产麻豆精品在线观看 | 欧美国产亚洲精品高清不卡 | 欧美视频在线观看视频 | 午夜宅男在线 | 五月婷婷在线视频 | 亚洲一区二区中文 | 久久93精品国产91久久综合 | 日本护士xxxxx高清免费 | 亚洲精品国产三级在线观看 | 模特视频一二三区 | 国产精品成人网 | 久久精品免费一区二区视 | 亚洲欧美男人天堂 | 亚洲伊人成人 | 日本护士xxxxxx.| 国产美女激情 | 中文字幕乱码视频中文字幕14 | 好爽好大www视频在线播放 |