变量改变时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>
Salin selepas log masuk
- 对
-
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>
Salin selepas log masuk
- 其实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.

Alat AI Hot

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool
Gambar buka pakaian secara percuma

Clothoff.io
Penyingkiran pakaian AI

AI Hentai Generator
Menjana ai hentai secara percuma.

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas



Dalam pembangunan PHP, kita sering menemui mesej ralat PHPNotice:Undefinedvariable. Mesej ralat ini bermakna kami telah menggunakan pembolehubah yang tidak ditentukan dalam kod. Walaupun mesej ralat ini tidak akan menyebabkan kod ranap, ia akan menjejaskan kebolehbacaan dan kebolehselenggaraan kod. Di bawah, artikel ini akan memperkenalkan anda kepada beberapa kaedah untuk menyelesaikan ralat ini. 1. Gunakan fungsi error_reporting(E_ALL) semasa proses pembangunan Dalam pembangunan PHP, kita boleh

Penyelesaian kepada PHPNotice:Undefinedvariable:arrin Dalam pengaturcaraan PHP, kita sering menemui mesej ralat "Notis:Undefinedvariable". Mesej ralat ini biasanya disebabkan oleh mengakses pembolehubah yang tidak ditentukan atau pembolehubah itu belum dimulakan. Untuk masalah ini, kita perlu mencari masalah dan menyelesaikannya dalam masa. Dalam artikel ini, kami akan menumpukan pada PHPNotice:Undefin

Cara menggunakan pembolehubah berangka dalam PHP Dalam PHP, pembolehubah berangka ialah jenis pembolehubah yang digunakan secara langsung tanpa pengisytiharan. Anda boleh menggunakan pembolehubah berangka untuk melakukan pengiraan matematik, perbandingan data dan operasi berangka lain. Artikel ini akan menerangkan cara menggunakan pembolehubah berangka dalam PHP dan memberikan contoh kod khusus. Mentakrifkan pembolehubah berangka Dalam PHP, mentakrifkan pembolehubah berangka adalah sangat mudah, hanya berikan nombor terus kepada pembolehubah. Berikut ialah contoh: $number=10; Dalam kod di atas, kami mentakrifkan nilai yang dipanggil $numb

Bagaimana dengan cepat menghapuskan ralat tidak ditentukan pembolehubah PHP? Dalam pembangunan PHP, kita sering menghadapi ralat pembolehubah yang tidak ditentukan. Ini kerana pembolehubah yang tidak ditetapkan digunakan dalam kod. Apabila menghadapi ralat seperti ini, kita perlu cepat mencari punca ralat dan menyelesaikannya. Berikut ialah beberapa cara untuk menyelesaikan masalah pembolehubah PHP ralat tidak ditentukan dengan cepat untuk membantu anda mencari dan membetulkan ralat dengan lebih cepat. Hidupkan pelaporan ralat: Apabila kami menghidupkan pelaporan ralat, PHP akan memaparkan semua ralat dan mesej amaran, termasuk ralat tidak ditentukan yang berubah-ubah. Kita boleh melakukan ini dengan membuka kod

Semasa membangunkan aplikasi PHP, jika anda menemui gesaan "Pembolehubah tidak ditentukan: sql", ini biasanya bermakna anda merujuk pembolehubah yang tidak ditentukan. Ini boleh disebabkan oleh banyak sebab, seperti salah ejaan nama pembolehubah, isu skop atau ralat sintaks dalam kod, dsb. Dalam artikel ini, kami akan meneroka pelbagai punca masalah ini dan menyediakan beberapa cara untuk menyelesaikannya. 1. Nama pembolehubah tersalah eja Dalam kod PHP anda, jika nama pembolehubah tidak betul atau salah eja, sistem

PHPNotice:Undefinedvariable:result bermaksud hasil pembolehubah yang tidak ditentukan dipanggil dalam program PHP, yang akan menyebabkan program menjana amaran peringkat Notis. Keadaan ini biasanya disebabkan oleh pengaturcara yang tidak mentakrifkan pembolehubah atau skop pembolehubah dengan betul semasa menulis kod PHP. Jika tidak diselesaikan tepat pada masanya, amaran tahap Notis ini boleh menyebabkan masalah dalam pengendalian program. Jadi, bagaimana untuk menyelesaikan PHPNotice:

Dalam PHP, anda boleh menggunakan simbol ampersand (&) untuk menghantar pembolehubah melalui rujukan dan bukannya mengikut nilai. Ini membolehkan pembolehubah asal diubah suai dalam fungsi atau kaedah. Terdapat dua cara untuk menghantar pembolehubah PHP dengan rujukan: Menggunakan simbol ampersand Menggunakan simbol ampersand dalam fungsi/pengisytiharan kaedah Menggunakan simbol ampersand dalam pengisytiharan fungsi/kaedah Apabila menghantar pembolehubah kepada fungsi/kaedah Dalam PHP, anda boleh menggunakan fungsi/ Simbol ampersand ( &) dalam pengisytiharan kaedah melepasi pembolehubah melalui rujukan. Berikut ialah penjelasan yang dikemas kini: Untuk menghantar pembolehubah rujukan dengan menggunakan simbol & dalam pengisytiharan fungsi/kaedah, anda perlu memasukkan simbol & sebelum nama parameter dalam definisi fungsi/kaedah. Ini menunjukkan bahawa parameter harus diluluskan dengan rujukan, membenarkan

Panduan pengenalan kepada prinsip pembangunan asas PHP7: Pelajari misteri kernel PHP dari awal Pengenalan: Dengan perkembangan pesat Internet, PHP, sebagai bahasa skrip bahagian pelayan yang popular, mempunyai pelbagai senario aplikasi. Walau bagaimanapun, ramai orang tahu sedikit tentang dalaman dan cara kerja PHP. Bagi pembangun yang ingin memahami kernel PHP secara mendalam, artikel ini akan menyediakan panduan pengenalan untuk membantu mereka mempelajari misteri kernel PHP dari awal. 1. Konsep asas proses penyusunan PHP kernel PHP Dalam proses penyusunan PHP
