变量改变时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.

PHP kekal sebagai alat yang kuat dan digunakan secara meluas dalam pengaturcaraan moden, terutamanya dalam bidang pembangunan web. 1) PHP mudah digunakan dan diintegrasikan dengan lancar dengan pangkalan data, dan merupakan pilihan pertama bagi banyak pemaju. 2) Ia menyokong penjanaan kandungan dinamik dan pengaturcaraan berorientasikan objek, sesuai untuk membuat dan mengekalkan laman web dengan cepat. 3) Prestasi PHP dapat ditingkatkan dengan caching dan mengoptimumkan pertanyaan pangkalan data, dan komuniti yang luas dan ekosistem yang kaya menjadikannya masih penting dalam timbunan teknologi hari ini.

Dalam PHP, rujukan lemah dilaksanakan melalui kelas lemah dan tidak akan menghalang pemungut sampah daripada menebus objek. Rujukan lemah sesuai untuk senario seperti sistem caching dan pendengar acara. Harus diingat bahawa ia tidak dapat menjamin kelangsungan hidup objek dan pengumpulan sampah mungkin ditangguhkan.

Kaedah \ _ \ _ membolehkan objek dipanggil seperti fungsi. 1. Tentukan kaedah \ _ \ _ supaya objek boleh dipanggil. 2. Apabila menggunakan sintaks $ OBJ (...), PHP akan melaksanakan kaedah \ _ \ _ invoke. 3. Sesuai untuk senario seperti pembalakan dan kalkulator, meningkatkan fleksibiliti kod dan kebolehbacaan.

Serat diperkenalkan dalam Php8.1, meningkatkan keupayaan pemprosesan serentak. 1) Serat adalah model konkurensi ringan yang serupa dengan coroutine. 2) Mereka membenarkan pemaju mengawal aliran pelaksanaan tugas secara manual dan sesuai untuk mengendalikan tugas I/O-intensif. 3) Menggunakan serat boleh menulis kod yang lebih cekap dan responsif.

Komuniti PHP menyediakan sumber dan sokongan yang kaya untuk membantu pemaju berkembang. 1) Sumber termasuk dokumentasi rasmi, tutorial, blog dan projek sumber terbuka seperti Laravel dan Symfony. 2) Sokongan boleh didapati melalui saluran StackOverflow, Reddit dan Slack. 3) Trend pembangunan boleh dipelajari dengan mengikuti RFC. 4) Integrasi ke dalam masyarakat dapat dicapai melalui penyertaan aktif, sumbangan kepada kod dan perkongsian pembelajaran.

PHP dan Python masing -masing mempunyai kelebihan sendiri, dan pilihannya harus berdasarkan keperluan projek. 1.Php sesuai untuk pembangunan web, dengan sintaks mudah dan kecekapan pelaksanaan yang tinggi. 2. Python sesuai untuk sains data dan pembelajaran mesin, dengan sintaks ringkas dan perpustakaan yang kaya.

PHP tidak mati, tetapi sentiasa menyesuaikan diri dan berkembang. 1) PHP telah menjalani beberapa lelaran versi sejak tahun 1994 untuk menyesuaikan diri dengan trend teknologi baru. 2) Ia kini digunakan secara meluas dalam e-dagang, sistem pengurusan kandungan dan bidang lain. 3) Php8 memperkenalkan pengkompil JIT dan fungsi lain untuk meningkatkan prestasi dan pemodenan. 4) Gunakan OPCACHE dan ikut piawaian PSR-12 untuk mengoptimumkan prestasi dan kualiti kod.

Masa depan PHP akan dicapai dengan menyesuaikan diri dengan trend teknologi baru dan memperkenalkan ciri -ciri inovatif: 1) menyesuaikan diri dengan pengkomputeran awan, kontena dan seni bina microservice, menyokong Docker dan Kubernetes; 2) memperkenalkan pengkompil JIT dan jenis penghitungan untuk meningkatkan prestasi dan kecekapan pemprosesan data; 3) Berterusan mengoptimumkan prestasi dan mempromosikan amalan terbaik.


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

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Versi Mac WebStorm
Alat pembangunan JavaScript yang berguna

MantisBT
Mantis ialah alat pengesan kecacatan berasaskan web yang mudah digunakan yang direka untuk membantu dalam pengesanan kecacatan produk. Ia memerlukan PHP, MySQL dan pelayan web. Lihat perkhidmatan demo dan pengehosan kami.

SublimeText3 Linux versi baharu
SublimeText3 Linux versi terkini

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma