变量改变时PHP内核做了些什么?_PHP教程
变量改变时PHP内核做了些什么?
引言
内容来自于《Extending and Embedding PHP》- Chaper 3 - Memory Management,加上自己的理解,对php中变量的引用计数、写时复制,写时改变,写时复制和改变做个”翻译“。
zval
看下面的内容之前先对zval这个结构体做个了解
<code class="hljs thrift" style="font-family: 'Courier New', sans-serif !important; line-height: 1.5 !important; font-size: 12px !important; background-color: rgb(245, 245, 245) !important; border: 1px solid rgb(204, 204, 204) !important; padding: 5px !important; border-top-left-radius: 3px !important; border-top-right-radius: 3px !important; border-bottom-right-radius: 3px !important; border-bottom-left-radius: 3px !important; display: block; overflow-x: auto; color: rgb(0, 0, 0); background-position: initial initial; background-repeat: initial initial;"><span class="hljs-keyword" style="color: rgb(0, 0, 255);">typedef</span> <span class="hljs-class"><span class="hljs-keyword" style="color: rgb(0, 0, 255);">struct</span> _<span class="hljs-title" style="color: rgb(163, 21, 21);">zval_struct</span> </span>{ zvalue_value value; zend_uint refcount; zend_uchar type; zend_uchar is_ref; } zval;</code>
zval结构体中共有4个元素,value是一个联合体,用来真正的存储zval的值,refcount用来计数该zval被多少个变量使用,type表示zval所存储的数据类型,is_ref用来标志该zval是否被引用。
引用计数
<code class="hljs xml" style="font-family: 'Courier New', sans-serif !important; line-height: 1.5 !important; font-size: 12px !important; background-color: rgb(245, 245, 245) !important; border: 1px solid rgb(204, 204, 204) !important; padding: 5px !important; border-top-left-radius: 3px !important; border-top-right-radius: 3px !important; border-bottom-right-radius: 3px !important; border-bottom-left-radius: 3px !important; display: block; overflow-x: auto; color: rgb(0, 0, 0); background-position: initial initial; background-repeat: initial initial;"><span class="php"><span class="hljs-preprocessor" style="color: rgb(43, 145, 175);"><?php </span> <span class="hljs-variable">$a</span> = <span class="hljs-string" style="color: rgb(163, 21, 21);">'Hello World'</span>; <span class="hljs-variable">$b</span> = <span class="hljs-variable">$a</span>; <span class="hljs-keyword" style="color: rgb(0, 0, 255);">unset</span>(<span class="hljs-variable">$a</span>); <span class="hljs-preprocessor" style="color: rgb(43, 145, 175);">?></span></span></span></code>
我们一起来剖析下上面这段代码:
-
$a = 'Hello World';
首先这句代码被执行,内核创建一个变量,并分配12字节的内存去存储字符串'Hello World'和末尾的NULL。 -
$b = $a;
接着执行这句代码,执行这句的时候内核里面发生了什么呢?- 对
$a
所指向的zval中的refcount进行加1操作。 -
将变量
$b
指向$a
所指向的zval。
在内核中大概是这样的,其中active_symbol_table
是当前的变量符号表<code class="hljs clojure" style="font-family: 'Courier New', sans-serif !important; line-height: 1.5 !important; font-size: 12px !important; background-color: rgb(245, 245, 245) !important; border: 1px solid rgb(204, 204, 204) !important; padding: 5px !important; border-top-left-radius: 3px !important; border-top-right-radius: 3px !important; border-bottom-right-radius: 3px !important; border-bottom-left-radius: 3px !important; display: block; overflow-x: auto; color: rgb(0, 0, 0); background-position: initial initial; background-repeat: initial initial;"> <span class="hljs-collection">{ zval *helloval; MAKE_STD_ZVAL<span class="hljs-list">(<span class="hljs-keyword" style="color: rgb(0, 0, 255);">helloval</span>)</span><span class="hljs-comment" style="color: green;">;</span> ZVAL_STRING<span class="hljs-list">(<span class="hljs-keyword" style="color: rgb(0, 0, 255);">helloval</span>, <span class="hljs-string" style="color: rgb(163, 21, 21);">"Hello World"</span>, <span class="hljs-number">1</span>)</span><span class="hljs-comment" style="color: green;">;</span> zend_hash_add<span class="hljs-list">(<span class="hljs-keyword" style="color: rgb(0, 0, 255);">EG</span><span class="hljs-list">(<span class="hljs-keyword" style="color: rgb(0, 0, 255);">active_symbol_table</span>)</span>, <span class="hljs-string" style="color: rgb(163, 21, 21);">"a"</span>, sizeof<span class="hljs-list">(<span class="hljs-string" style="color: rgb(163, 21, 21);">"a"</span>)</span>, &helloval, sizeof<span class="hljs-list">(<span class="hljs-keyword" style="color: rgb(0, 0, 255);">zval*</span>)</span>, NULL)</span><span class="hljs-comment" style="color: green;">;</span> ZVAL_ADDREF<span class="hljs-list">(<span class="hljs-keyword" style="color: rgb(0, 0, 255);">helloval</span>)</span><span class="hljs-comment" style="color: green;">;</span> zend_hash_add<span class="hljs-list">(<span class="hljs-keyword" style="color: rgb(0, 0, 255);">EG</span><span class="hljs-list">(<span class="hljs-keyword" style="color: rgb(0, 0, 255);">active_symbol_table</span>)</span>, <span class="hljs-string" style="color: rgb(163, 21, 21);">"b"</span>, sizeof<span class="hljs-list">(<span class="hljs-string" style="color: rgb(163, 21, 21);">"b"</span>)</span>, &helloval, sizeof<span class="hljs-list">(<span class="hljs-keyword" style="color: rgb(0, 0, 255);">zval*</span>)</span>, NULL)</span><span class="hljs-comment" style="color: green;">;</span> }</span></code>
登入後複製
- 对
-
unset($a);
这句代码执行后,内核会将a对应的zval结构体中的refcount计数减一, b还和原来一样
写时复制
<code class="hljs xml" style="font-family: 'Courier New', sans-serif !important; line-height: 1.5 !important; font-size: 12px !important; background-color: rgb(245, 245, 245) !important; border: 1px solid rgb(204, 204, 204) !important; padding: 5px !important; border-top-left-radius: 3px !important; border-top-right-radius: 3px !important; border-bottom-right-radius: 3px !important; border-bottom-left-radius: 3px !important; display: block; overflow-x: auto; color: rgb(0, 0, 0); background-position: initial initial; background-repeat: initial initial;"><span class="php"><span class="hljs-preprocessor" style="color: rgb(43, 145, 175);"><?php </span> <span class="hljs-variable">$a</span> = <span class="hljs-number">1</span>; <span class="hljs-variable">$b</span> = <span class="hljs-variable">$a</span>; <span class="hljs-variable">$b</span> += <span class="hljs-number">5</span>; <span class="hljs-preprocessor" style="color: rgb(43, 145, 175);">?></span></span></span></code>
上面这段代码执行完之后,一般肯定希望$a=1,$b=6
,但是如果像引用计数那样,$a
和$b
指向相同的zval,修改$b
之后$a
不是也变了?
这个具体是怎么实现的呢,我们一起来看下:
-
$a = 1;
内核创建一个zval,并分配4个字节存储数字1。 -
$b = $a;
这一步和引用计数中的第二步一样,将$b
指向和$a
相同的zval,并将zval中的引用计数值refcount加1。 -
$b += 5;
关键是这一步,这一步骤发生了什么呢,怎么确保修改之后不影响$a
。- 其实Zend内核在改变zval之前都会去进行
get_var_and_separete
操作,如果recfount>1,就需要分离就创建新的zval返回,否则直接返回变量所指向的zval,下面看看如何分离产生新的zval。 - 复制一个和
$b
所指向zval一样的zval。 - 将
$b
所指向的zval中的refcount计数减1。 - 初始化生成的新zval,设置refcount=1,is_ref=0。
- 让
$b
指向新生成的zval。 -
对新生成的zval进行操作,这就是写时复制。
下面看看内核中分离时的主要代码:<code class="hljs lasso" style="font-family: 'Courier New', sans-serif !important; line-height: 1.5 !important; font-size: 12px !important; background-color: rgb(245, 245, 245) !important; border: 1px solid rgb(204, 204, 204) !important; padding: 5px !important; border-top-left-radius: 3px !important; border-top-right-radius: 3px !important; border-bottom-right-radius: 3px !important; border-bottom-left-radius: 3px !important; display: block; overflow-x: auto; color: rgb(0, 0, 0); background-position: initial initial; background-repeat: initial initial;"> zval <span class="hljs-subst">*</span>get_var_and_separate(char <span class="hljs-subst">*</span>varname, int varname_len TSRMLS_DC) { zval <span class="hljs-subst">**</span>varval, <span class="hljs-subst">*</span>varcopy; <span class="hljs-keyword" style="color: rgb(0, 0, 255);">if</span> (zend_hash_find(EG(active_symbol_table), varname, varname_len <span class="hljs-subst">+</span> <span class="hljs-number">1</span>, (<span class="hljs-literal">void</span><span class="hljs-subst">**</span>)<span class="hljs-subst">&</span>varval) <span class="hljs-subst">==</span> FAILURE) { <span class="hljs-comment" style="color: green;">/* Variable doesn't actually exist fail out */</span> <span class="hljs-keyword" style="color: rgb(0, 0, 255);">return</span> <span class="hljs-built_in" style="color: rgb(0, 0, 255);">NULL</span>; } <span class="hljs-keyword" style="color: rgb(0, 0, 255);">if</span> ((<span class="hljs-subst">*</span>varval)<span class="hljs-subst">-></span>is_ref <span class="hljs-subst">||</span> (<span class="hljs-subst">*</span>varval)<span class="hljs-subst">-></span>refcount <span class="hljs-subst"> <span class="hljs-number">2</span>) { <span class="hljs-comment" style="color: green;">/* varname is the only actual reference, * or it's a full reference to other variables * either way: no separating to be done */</span> <span class="hljs-keyword" style="color: rgb(0, 0, 255);">return</span> <span class="hljs-subst">*</span>varval; } <span class="hljs-comment" style="color: green;">/* Otherwise, make a copy of the zval* value */</span> MAKE_STD_ZVAL(varcopy); varcopy <span class="hljs-subst">=</span> <span class="hljs-subst">*</span>varval; <span class="hljs-comment" style="color: green;">/* Duplicate any allocated structures within the zval* */</span> zval_copy_ctor(varcopy); <span class="hljs-comment" style="color: green;">/* Remove the old version of varname * This will decrease the refcount of varval in the process */</span> zend_hash_del(EG(active_symbol_table), varname, varname_len <span class="hljs-subst">+</span> <span class="hljs-number">1</span>); <span class="hljs-comment" style="color: green;">/* Initialize the reference count of the * newly created value and attach it to * the varname variable */</span> varcopy<span class="hljs-subst">-></span>refcount <span class="hljs-subst">=</span> <span class="hljs-number">1</span>; varcopy<span class="hljs-subst">-></span>is_ref <span class="hljs-subst">=</span> <span class="hljs-number">0</span>; zend_hash_add(EG(active_symbol_table), varname, varname_len <span class="hljs-subst">+</span> <span class="hljs-number">1</span>, <span class="hljs-subst">&</span>varcopy, sizeof(zval<span class="hljs-subst">*</span>), <span class="hljs-built_in" style="color: rgb(0, 0, 255);">NULL</span>); <span class="hljs-comment" style="color: green;">/* Return the new zval* */</span> <span class="hljs-keyword" style="color: rgb(0, 0, 255);">return</span> varcopy; }</span></code>
登入後複製
- 其实Zend内核在改变zval之前都会去进行
写时改变
<code class="hljs xml" style="font-family: 'Courier New', sans-serif !important; line-height: 1.5 !important; font-size: 12px !important; background-color: rgb(245, 245, 245) !important; border: 1px solid rgb(204, 204, 204) !important; padding: 5px !important; border-top-left-radius: 3px !important; border-top-right-radius: 3px !important; border-bottom-right-radius: 3px !important; border-bottom-left-radius: 3px !important; display: block; overflow-x: auto; color: rgb(0, 0, 0); background-position: initial initial; background-repeat: initial initial;"><span class="php"><span class="hljs-preprocessor" style="color: rgb(43, 145, 175);"><?php </span> <span class="hljs-variable">$a</span> = <span class="hljs-number">1</span>; <span class="hljs-variable">$b</span> = &<span class="hljs-variable">$a</span>; <span class="hljs-variable">$b</span> += <span class="hljs-number">5</span>; <span class="hljs-preprocessor" style="color: rgb(43, 145, 175);">?></span></span></span></code>
上面这段代码执行完之后一般希望是:$a == $b == 6
。这个又是怎么实现的呢?
-
$a = 1;
这一步骤和写时复制中的第一步一样。 -
$b = &$a;
这一步骤内核会将$b
指向$a
所指向的zval,将zval中的refcount加1,并将zval中的is_ref置为1。 -
$b += 5;
这一步骤和写时复制中的第三步骤一样,但是内核中发生的事情却不一样。- 内核看到
$b
进行变化的时候,也会执行get_var_and_separate函数,看是否需要分离。 - 如果
(*varval)->is_ref
的话也会直接返回$b
所指向的zval,不去分离产生新的zval,不管zval的refcount是否>1。 - 这时候再去修改
$b
值,$a
的值也就改变了,因为他们指向相同的zval。
- 内核看到
分离的问题
说道现在聪明的你可能已经看出点问题了,如果一个zval结构体既有refcount计数又有is_ref引用这个时候怎么办?
<code class="hljs xml" style="font-family: 'Courier New', sans-serif !important; line-height: 1.5 !important; font-size: 12px !important; background-color: rgb(245, 245, 245) !important; border: 1px solid rgb(204, 204, 204) !important; padding: 5px !important; border-top-left-radius: 3px !important; border-top-right-radius: 3px !important; border-bottom-right-radius: 3px !important; border-bottom-left-radius: 3px !important; display: block; overflow-x: auto; color: rgb(0, 0, 0); background-position: initial initial; background-repeat: initial initial;"><span class="php"><span class="hljs-preprocessor" style="color: rgb(43, 145, 175);"><?php </span> <span class="hljs-variable">$a</span> = <span class="hljs-number">1</span>; <span class="hljs-variable">$b</span> = <span class="hljs-variable">$a</span>; <span class="hljs-variable">$c</span> = &<span class="hljs-variable">$a</span>; <span class="hljs-preprocessor" style="color: rgb(43, 145, 175);">?></span></span></span></code>
如果出现上面这种情况的时候,如果$a、$b、$c
指向同一个zval结构体,进行改变的时候Zend到底去听谁的?其实这个地方不会指向同一个zval了。
如果对一个is_ref = 0 && refcount >1
的zval进行写时改变这种赋值形式(就是引用赋值)的时候,Zend会将等号右边的变量分离出来一个新的zval,
对这个zval进行初始化,对之前的zval的refcount进行减1操作,让等号左边的变量指向这个新的zval,refcount进行加1操作,is_ref=1。看看下面这张图片
<code class="hljs xml" style="font-family: 'Courier New', sans-serif !important; line-height: 1.5 !important; font-size: 12px !important; background-color: rgb(245, 245, 245) !important; border: 1px solid rgb(204, 204, 204) !important; padding: 5px !important; border-top-left-radius: 3px !important; border-top-right-radius: 3px !important; border-bottom-right-radius: 3px !important; border-bottom-left-radius: 3px !important; display: block; overflow-x: auto; color: rgb(0, 0, 0); background-position: initial initial; background-repeat: initial initial;"><span class="php"><span class="hljs-preprocessor" style="color: rgb(43, 145, 175);"><?php </span> <span class="hljs-variable">$a</span> = <span class="hljs-number">1</span>; <span class="hljs-variable">$b</span> = &<span class="hljs-variable">$a</span>; <span class="hljs-variable">$c</span> = <span class="hljs-variable">$a</span>; <span class="hljs-preprocessor" style="color: rgb(43, 145, 175);">?></span></span></span></code>
上面这又是另外一种情况,在is_ref = 1
的情况下,试图单纯的进行refcount+1操作的时候会分离出来一个新的zval给等号左边的变量,并初始化他,看看下面这张图片
参考文献
1.《Extending and Embedding PHP》- Chaper 3 - Memory Management.

熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

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

在PHP開發中,我們常常會遇到PHPNotice:Undefinedvariable的錯誤提示。這個錯誤提示表示我們在程式碼中使用了一個未定義的變數。雖然這個錯誤提示不會導致程式碼崩潰,但是它會影響程式碼的可讀性和可維護性。下面,本文將為大家介紹一些解決這個錯誤的方法。 1.在開發過程中使用error_reporting(E_ALL)函數在PHP開發中,我們可

PHPNotice:Undefinedvariable:arrin的解決方法在PHP程式設計中,我們常常會遇到「Notice:Undefinedvariable」這個錯誤提示。這個錯誤提示一般是因為訪問了未定義的變數或變數未被初始化所導致的。對於這個問題,我們需要及時找到問題並解決。在本文中,我們將重點討論PHPNotice:Undefin

如何在PHP中使用數字變數在PHP中,數字變數是一種無需聲明而直接使用的變數類型。可以使用數字變數進行數學計算、數據比較和其他數值操作。本文將介紹如何在PHP中使用數字變量,並提供具體的程式碼範例。定義數字變數在PHP中,定義數字變數非常簡單,只要直接給變數一個數字。下面是一個範例:$number=10;在上面的程式碼中,我們定義了一個名為$numb

如何快速排除PHP變數未定義錯誤?在PHP開發中,常會遇到變數未定義的錯誤。這是因為在程式碼中使用了一個未賦值的變數。當遇到這種錯誤時,我們需要迅速找到錯誤的原因並解決它。以下是一些快速排除PHP變數未定義錯誤的方法,可幫助您更快定位和修復錯誤。開啟錯誤報告:當我們開啟錯誤報告時,PHP會顯示所有的錯誤和警告訊息,包括變數未定義錯誤。我們可以透過在代碼的開

在PHP中,您可以使用和號(&)符號將變數按引用而不是按值傳遞。這樣可以在函數或方法內修改原始變數。主要有兩種方式可以透過引用傳遞PHP變數:使用ampersand符號在函數/方法聲明中使用和符號將變數傳遞給函數/方法時在函數/方法聲明中使用和號在PHP中,您可以使用函數/方法宣告中的和號符號(&)透過引用傳遞變數。以下是更新的解釋:要透過在函數/方法聲明中使用&符號來傳遞引用變量,您需要在函數/方法定義中在參數名稱之前包含&符號。這表示參數應該透過引用傳遞,允許

在開發PHP應用程式時,如果遇到了"Undefinedvariable:sql"的提示,這通常意味著您正在引用一個未定義的變數。這可能是由於許多原因引起的,例如變數名稱拼字錯誤、作用域問題或程式碼中的語法錯誤等。在本篇文章中,我們將探討這個問題的各種原因,並提供一些解決這個問題的方法。 1.變數名稱拼字錯誤在您的PHP程式碼中,如果變數名稱不正確或拼字錯誤,系

PHPNotice:Undefinedvariable:result是指在PHP程式中呼叫了一個未定義的變數result,這會導致程式產生Notice層級的警告。這種情況一般是由於程式設計師在編寫PHP程式碼時未正確定義變數或變數的作用域所造成的。如果不及時解決,這種Notice層級的警告可能會導致程式的運行出現問題。那麼,如何解決PHPNotice:

在PHP程式設計中,變數是儲存值的基本單元,用於在程式執行過程中儲存和使用資料。在PHP中,變數可以被賦予不同的資料類型,包括整數、浮點型、字串、陣列等等。在本文中,我們將介紹PHP程式設計中常見的變數及其用法。簡單變數簡單變數是最常見的變數類型,它們可以儲存整數、浮點數、字串等常規資料類型。在PHP中,未定義變數的初始值為NULL。以下是幾個實例:整數變數:$
