我們已經知道php變數在核心中其實是透過zval結構來實現的,也初步了解如何設定一個zval結構的類型和值。
在code的時候,很希望在核心中建立的zval可以讓使用者在PHP語言裡以變數的形式使用,為了實現這個功能, 我們要先建立一個zval。最容易想到的辦法就是建立一個zval指針, 然後申請一塊記憶體並讓指針指向它。如果你腦海裡浮現出了malloc(sizeof(zval))的影子, 那麼請你立即剎車,不要用malloc來做這件事情,內核給我們提供了相應的宏來處理這件事, 理由和以前一樣:為了程式碼漂亮並保持版本升級時的相容性。 這個巨集的是:MAKE_STD_ZVAL(pzv)。這個宏會用核心的方式來申請一塊記憶體並將其位址付給pzv, 並初始化它的refcount和is_ref兩個屬性,更棒的是,它不但會自動的處理記憶體不足問題, 還會在記憶體中選個最優的位置來申請。
作為資料的容器,我們常常需要跟變數打交道,不管這個變數是數字、陣列、字串、物件或其他,因而可以說變數是構成語言的不可或缺的基礎。本文為PHP內核探索之變數的第一篇,主要介紹zval的基本知識,包含以下幾個面向的內容:
- Zval的基本結構
- 查看zval的方法:debug_zval_dump和xdebug
- Zval的原理,COW等
由於寫作倉促,難免會有錯誤,歡迎指出。
一、Zval的基本結構
Zval是PHP中最重要的資料結構之一(另一個比較重要的資料結構是hash table),它包含了PHP中的變數值和類型的相關資訊。它是一個struct,基本結構為:
<span>struct</span><span> _zval_struct { zvalue_value value; </span><span>/*</span><span> value </span><span>*/</span><span> zend_uint refcount__gc; </span><span>/*</span><span> variable ref count </span><span>*/</span><span> zend_uchar type; </span><span>/*</span><span> active type </span><span>*/</span><span> zend_uchar is_ref__gc; </span><span>/*</span><span> if it is a ref variable </span><span>*/</span><span> }; typedef </span><span>struct</span> _zval_struct zval;
其中:
1. zval_value value
變數的實際值,具體來說是一個zvalue_value的聯合體(union):
<span>typedef union _zvalue_value { </span><span>long</span> lval; <span>/*</span><span> long value </span><span>*/</span> <span>double</span> dval; <span>/*</span><span> double value </span><span>*/</span> <span>struct</span> { <span>/*</span><span> string </span><span>*/</span> <span>char</span> *<span>val; </span><span>int</span><span> len; } str; HashTable </span>*ht; <span>/*</span><span> hash table value,used for array </span><span>*/</span><span> zend_object_value obj; </span><span>/*</span><span> object </span><span>*/</span><span> } zvalue_value;</span>
2. zend_uint refcount__gc
該值實際上是一個計數器,用來保存有多少變量(或者符號,symbols,所有的符號都存在符號表(symble table)中, 不同的作用域使用不同的符號表,關於這一點,我們之後會論述)指向該zval。在變數生成時,其refcount=1,典型的賦值運算如$a = $b會令zval的refcount加1,而unset操作會對應的減1。在PHP5.3之前,使用引用計數的機制來實作GC,如果一個zval的refcount較少到0,那麼Zend引擎會認為沒有任何變數指向該zval,因此會釋放該zval所佔的記憶體空間。但,事情有時並不會那麼簡單。後面我們會看到,單純的引用計數機制無法GC掉循環引用的zval,即使指向該zval的變數已經被unset,從而導致了記憶體洩漏(Memory Leak)。
3. zend_uchar type
此欄位用於表示變數的實際類型。在開始學習PHP的時候,我們已經知道,PHP中的變數包括四種標量類型(bool,int,float,string),兩種複合類型(array, object)和兩種特殊的類型(resource 和NULL)。在zend內部,這些類型對應於下面的巨集(程式碼位置 phpsrc/Zend/zend.h):
<span>#define</span> IS_NULL 0 <span>#define</span> IS_LONG 1 <span>#define</span> IS_DOUBLE 2 <span>#define</span> IS_BOOL 3 <span>#define</span> IS_ARRAY 4 <span>#define</span> IS_OBJECT 5 <span>#define</span> IS_STRING 6 <span>#define</span> IS_RESOURCE 7 <span>#define</span> IS_CONSTANT 8 <span>#define</span> IS_CONSTANT_ARRAY 9 <span>#define</span> IS_CALLABLE 10
4. is_ref__gc
這個欄位用來標記變數是否是引用變數。對於普通的變量,該值為0,而對於引用型的變量,該值為1。這個變數會影響zval的共享、分離等。關於這點,我們之後會有論述。
正如名字所示,ref_count__gc和is_ref__gc是PHP的GC機制所需的很重要的兩個字段,這兩個字段的值,可以透過xdebug等調試工具查看。
二、xdebug的安裝配置
Xdebug是一個開源的PHP 效能分析和debug工具。雖然對於一般的程式調試,var_dump,echo,print,debug_backtrace等常見的調試工具已經基本夠用,但對於一些複雜的調試和性能測試,xdebug絕對是一個很好的幫手(其他的如Xhprof等工具也很優秀)。
本文的基本環境:
安裝Xdebug的基本流程為(實際上是原始碼編譯一個擴充):
1. 下載源碼包.
下載網址為:http://www.xdebug.org/docs/install
本文中下载的版本为:Xdebug-2.6.tar.gz
2. 解压
<span>tar</span> xvzf xdebug-<span>2.6</span>.<span>tar</span>.gz
3. 在xdebug的目录执行phpize
4. ./configure 配置
5. Make&& make install
这会生成xdebug.so扩展文件(zend_extension),位置在xdebug/modules
6. 在php.ini中加载xdebug扩展
zend_extension=your-xdebug-path/xdebug.so
7. 添加xdebug的配置
xdebug.profiler_enable =<span> on xdebug.default_enable </span>=<span> on xdebug.trace_output_dir</span>="/tmp/xdebug"<span> xdebug.trace_output_name </span>=<span> trace.%c.%p xdebug.profiler_output_dir</span>="/tmp/xdebug"<span> xdebug.profiler_output_name</span>="cachegrind.out.%s"
这里不再详细介绍各个配置项的含义,详细的请看:http://www.xdebug.org/docs/all
现在,PHP中,应该已经有了Xdebug的扩展信息(php –m,也可以phpinfo()):
在Xdebug中,可以通过xdebug_debug_zval打印Zval的信息:
<?php $a = array( 'test' ); $a[] = &$a; xdebug_debug_zval( 'a' );
3. Zval的更多原理
(注,本部分主要参考:http://derickrethans.nl/collecting-garbage-phps-take-on-variables.html, 作者Derick Rethans是一位优秀的PHP内核专家,在全世界做过多次报告,都有相关的pdf下载,这里(http://derickrethans.nl/talks.html )有作者每次演讲的记录,很多都值得我们深入去学习研究)
前面我们已经说过,PHP使用Zval这种结构来保存变量,这里我们将继续追踪zval的更多细节。
1. 创建变量时,会创建一个zval.
$str = "test zval"; xdebug_debug_zval('str');
输出结果:
str: (refcount=1, is_ref=0)='test zval'
当使用$str="test zval";来创建变量时,会在当前作用域的符号表中插入新的符号(str),由于该变量是一个普通的变量,因此会生成一个refcount=1且is_ref=0的zval容器。也就是说,实际上是这样的:
2. 变量赋值给另外一个变量时,会增加zval的refcount值。
$str = "test zval"; $str2 = $str; xdebug_debug_zval('str'); xdebug_debug_zval('str2');
输出结果:
str: (refcount=2, is_ref=0)=<span>'test zval' str2: (refcount</span>=2, is_ref=0)='test zval'
同时我们看到,str和是str2这两个symbol的zval结构是一样的。这里其实是PHP所做的一个优化,由于str和str2都是普通变量,因而它们指向了同一个zval,而没有为str2开辟单独的zval。这么做,可以在一定程度上节省内存。这时的str,str2与zval的对应关系是这样的:
3. 使用unset时,对减少相应zval的refcount值
$str = "test zval"; $str3 = $str2 = $str; xdebug_debug_zval('str'); unset($str2,$str3) xdebug_debug_zval('str');
结果为:
str: (refcount=3, is_ref=0)=<span>'test zval' str: (refcount</span>=1, is_ref=0)='test zval'
由于unset($str2,$str3)会将str2和str3从符号表中删除,因此,在unset之后,只有str指向该zval,如下图所示:
现在如果执行unset($str),则由于zval的refcount会减少到0,该zval会从内存中清理。这当然是最理想的情况。
但是事情并不总是那么乐观。
4. 数组变量与普通变量生成的zval非常类似,单也有很大不同
与标量这些普通变量不同,数组和对象这类复合型的变量在生成zval时,会为每个item项生成一个zval容器。例如:
$ar = array( 'id' => 38, 'name' => 'shine' ); <span>xdebug_debug_zval('ar');</span>
打印出zval的结构是:
ar: (refcount=1, is_ref=0)=<span>array ( 'id' </span>=> (refcount=1, is_ref=0)=38,<span> 'name' </span>=> (refcount=1, is_ref=0)=<span>'shine' )</span>
如下图所示:
可以看出,变量$ar生成的过程中,共生成了3个zval容器(红色部分标注)。对于每个zval而言,refcount的增减规则与普通变量的相同。例如,我们在数组中添加另外一个元素,并把$ar['name']的值赋给它:
$ar = array( 'id' => 38, 'name' => 'shine' ); $ar['test'] = $ar['name']; xdebug_debug_zval('ar');
则打印出的zval为:
ar: (refcount=1, is_ref=0)=<span>array ( 'id' </span>=> (refcount=1, is_ref=0)=38,<span> 'name' </span>=> (refcount=2, is_ref=0)='shine',<span> 'test' </span>=> (refcount=2, is_ref=0)=<span>'shine' )</span>
如同普通变量一样,这时候,name和test这两个symbol指向同一个zval:
同样的,从数组中移除元素时,会从符号表中删除相应的符号,同时减少对应zval的refcount值。同样,如果zval的refcount值减少到0,那么就会从内存中删除该zval:
$ar = array( 'id' => 38, 'name' => 'shine' ); $ar['test'] = $ar['name']; unset($ar['test'],$ar['name']); xdebug_debug_zval('ar');
输出结果为:
ar: (refcount=1, is_ref=0)=array ('id' => (refcount=1, is_ref=0)=38)
5. 引用的出现,会令zval的规则变得复杂
在加入引用之后,情况会变的稍微复杂一点。例如,在数组中添加对本身的引用:
$a = $array('one'); $a[] = &$a; xdebug_debug_zval('a');
输出的结果:
a: (refcount=2, is_ref=1)=<span>array ( </span>0 => (refcount=1, is_ref=0)='one', 1 => (refcount=2, is_ref=1)=<span>... )</span>
上述输出中,…表示指向原始数组,因而这是一个循环的引用。如下图所示:
现在,我们对$a执行unset操作,这会在symbol table中删除相应的symbol,同时,zval的refcount减1(之前为2),也就是说,现在的zval应该是这样的结构:
(refcount=1, is_ref=1)=<span>array ( </span>0 => (refcount=1, is_ref=0)='one', 1 => (refcount=1, is_ref=1)=<span>... )</span>
也就是下图所示的结构:
这时,不幸的事情发生了!
Unset之后,虽然没有变量指向该zval,但是该zval却不能被GC(指PHP5.3之前的单纯引用计数机制的GC)清理掉,因为zval的refcount均大于0。这样,这些zval实际上会一直存在内存中,直到请求结束(参考SAPI的生命周期)。在此之前,这些zval占据的内存不能被使用,便白白浪费了,换句话说,无法释放的内存导致了内存泄露。
如果这种内存泄露仅仅发生了一次或者少数几次,倒也还好,但如果是成千上万次的内存泄露,便是很大的问题了。尤其在长时间运行的脚本中(例如守护程序,一直在后台执行不会中断),由于无法回收内存,最终会导致系统“再无内存可用”。
6. zval分离(Copy on write和change on write)
前面我们已经介绍过,在变量赋值的过程中例如$b = $a,为了节省空间,并不会为$a和$b都开辟单独的zval,而是使用共享zval的形式:
那么问题来了:如果其中一个变量发生变化时,如何处理zval的共享问题?
对于这样的代码:
$a = "a simple test"; $b = $a; echo "before write:".PHP_EOL; xdebug_debug_zval('a'); xdebug_debug_zval('b'); $b = "thss"; echo "after write:".PHP_EOL; xdebug_debug_zval('a'); xdebug_debug_zval('b');
打印的结果是:
<span>before write: a: (refcount</span>=2, is_ref=0)=<span>'a simple test' b: (refcount</span>=2, is_ref=0)=<span>'a simple test' after write: a: (refcount</span>=1, is_ref=0)=<span>'a simple test' b: (refcount</span>=1, is_ref=0)='thss'
起初,符号表中a和b指向了同一个zval(这么做的原因是节省内存),而后$b发生了变化,Zend会检查b指向的zval的refcount是否为1,如果是1,那么说明只有一个符号指向该zval,则直接更改zval。否则,说明这是一个共享的zval,需要将该zval分离出去,以保证单独变化互不影响,这种机制叫做COW –Copy on write。在很多场景下,COW都是一种比较高效的策略。
那么对于引用变量呢?
$a = 'test'; $b = &$a; echo "before change:".PHP_EOL; xdebug_debug_zval('a'); xdebug_debug_zval('b'); $b = 12; echo "after change:".PHP_EOL; xdebug_debug_zval('a'); xdebug_debug_zval('b'); unset($b); echo "after unset:".PHP_EOL; xdebug_debug_zval('a'); xdebug_debug_zval('b');
输出的结果为:
<span>before change: a: (refcount</span>=2, is_ref=1)=<span>'test' b: (refcount</span>=2, is_ref=1)=<span>'test' after change: a: (refcount</span>=2, is_ref=1)=12<span> b: (refcount</span>=2, is_ref=1)=12<span> after unset: a: (refcount</span>=1, is_ref=0)=12
可以看出,在改变了$b的值之后,Zend会检查zval的is_ref检查是否是引用变量,如果是引用变量,则直接更改即可,否则,需要执行刚刚提到的zval分离。由于$a 和 $b是引用变量,因而更改共享的zval实际上也间接更改了$a的值。而在unset($b)之后,变量$b从符号表中删除了。
这里也说明一个问题,unset并不是清除zval,而只是从符号表中删除相应的symbol。这样一来,之前很多的关于引用的疑问也可以理解了(下一节我们将深入探索PHP的引用)。
以上就介绍了PHP内核探索之变量Zval,包括了变量Zval方面的内容,希望对PHP教程有兴趣的朋友有所帮助。

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

PHP不是在消亡,而是在不斷適應和進化。 1)PHP從1994年起經歷多次版本迭代,適應新技術趨勢。 2)目前廣泛應用於電子商務、內容管理系統等領域。 3)PHP8引入JIT編譯器等功能,提升性能和現代化。 4)使用OPcache和遵循PSR-12標準可優化性能和代碼質量。

PHP的未來將通過適應新技術趨勢和引入創新特性來實現:1)適應云計算、容器化和微服務架構,支持Docker和Kubernetes;2)引入JIT編譯器和枚舉類型,提升性能和數據處理效率;3)持續優化性能和推廣最佳實踐。

在PHP中,trait適用於需要方法復用但不適合使用繼承的情況。 1)trait允許在類中復用方法,避免多重繼承複雜性。 2)使用trait時需注意方法衝突,可通過insteadof和as關鍵字解決。 3)應避免過度使用trait,保持其單一職責,以優化性能和提高代碼可維護性。

依賴注入容器(DIC)是一種管理和提供對象依賴關係的工具,用於PHP項目中。 DIC的主要好處包括:1.解耦,使組件獨立,代碼易維護和測試;2.靈活性,易替換或修改依賴關係;3.可測試性,方便注入mock對象進行單元測試。

SplFixedArray在PHP中是一種固定大小的數組,適用於需要高性能和低內存使用量的場景。 1)它在創建時需指定大小,避免動態調整帶來的開銷。 2)基於C語言數組,直接操作內存,訪問速度快。 3)適合大規模數據處理和內存敏感環境,但需謹慎使用,因其大小固定。

PHP通過$\_FILES變量處理文件上傳,確保安全性的方法包括:1.檢查上傳錯誤,2.驗證文件類型和大小,3.防止文件覆蓋,4.移動文件到永久存儲位置。

JavaScript中處理空值可以使用NullCoalescingOperator(??)和NullCoalescingAssignmentOperator(??=)。 1.??返回第一個非null或非undefined的操作數。 2.??=將變量賦值為右操作數的值,但前提是該變量為null或undefined。這些操作符簡化了代碼邏輯,提高了可讀性和性能。


熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

SAP NetWeaver Server Adapter for Eclipse
將Eclipse與SAP NetWeaver應用伺服器整合。

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

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

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!