变量改变时PHP内核做了些什么?
变量改变时PHP内核做了些什么?
看下面的内容之前先对zval这个结构体做个了解<span class="hljs-keyword"><br>
</span>
<ol class="dp-j"><li class="alt"><span><span>typedef struct _zval_struct { </span></span></li><li><span> zvalue_value value; </span></li><li class="alt"><span> zend_uint refcount; </span></li><li><span> zend_uchar type; </span></li><li class="alt"><span> zend_uchar is_ref; </span></li><li><span>} zval; </span></li></ol>
zval结构体中共有4个元素,value是一个联合体,用来真正的存储zval的值,refcount用来计数该zval被多少个变量使用,type表示zval所存储的数据类型,is_ref用来标志该zval是否被引用。
引用计数<span class="php"><span class="hljs-preprocessor"><br />
</span></span>
<ol class="dp-j"><li class="alt"><span><span><php </span></span></li><li><span> $a = <span class="string">'Hello World'</span><span>; </span></span></li><li class="alt"><span> $b = $a; </span></li><li><span> unset($a); </span></li><li class="alt"><span>> </span></li></ol>
我们一起来剖析下上面这段代码:
-
$a = 'Hello World';
首先这句代码被执行,内核创建一个变量,并分配12字节的内存去存储字符串'Hello World'和末尾的NULL。 -
$b = $a;
接着执行这句代码,执行这句的时候内核里面发生了什么呢?-
对
$a
所指向的zval中的refcount进行加1操作。 -
将变量
$b
指向$a
所指向的zval。
在内核中大概是这样的,其中active_symbol_table
是当前的变量符号表<span class="hljs-collection"><br /> </span>
<ol class="dp-j"><li class="alt"><span><span>{ </span></span></li><li><span> zval *helloval; </span></li><li class="alt"><span> MAKE_STD_ZVAL(helloval); </span></li><li><span> ZVAL_STRING(helloval, <span class="string">"Hello World"</span><span>, </span><span class="number">1</span><span>); </span></span></li><li class="alt"><span> zend_hash_add(EG(active_symbol_table), <span class="string">"a"</span><span>, sizeof(</span><span class="string">"a"</span><span>), </span></span></li><li><span> &helloval, sizeof(zval*), NULL); </span></li><li class="alt"><span> ZVAL_ADDREF(helloval); </span></li><li><span> zend_hash_add(EG(active_symbol_table), <span class="string">"b"</span><span>, sizeof(</span><span class="string">"b"</span><span>), </span></span></li><li class="alt"><span> &helloval, sizeof(zval*), NULL); </span></li><li><span>} </span></li></ol>
Copy after login
-
-
unset($a);
这句代码执行后,内核会将a对应的zval结构体中的refcount计数减一, b还和原来一样
写时复制
<ol class="dp-c"><li class="alt"><span><span><?php </span></span></li><li><span> <span class="vars">$a</span><span> = 1; </span></span></li><li class="alt"><span> <span class="vars">$b</span><span> = </span><span class="vars">$a</span><span>; </span></span></li><li><span> <span class="vars">$b</span><span> += 5; </span></span></li><li class="alt"><span>?> </span></li></ol>
上面这段代码执行完之后,一般肯定希望$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进行操作,这就是写时复制。
下面看看内核中分离时的主要代码:<br type="_moz" />
<ol class="dp-c"><li class="alt"><span><span>zval *get_var_and_separate(char *varname, int varname_len TSRMLS_DC) </span></span></li><li><span>{ </span></li><li class="alt"><span> zval **varval, *varcopy; </span></li><li><span> <span class="keyword">if</span><span> (zend_hash_find(EG(active_symbol_table), </span></span></li><li class="alt"><span> varname, varname_len + 1, (void**)&varval) == FAILURE) { </span></li><li><span> <span class="comment">/* Variable doesn't actually exist fail out */</span><span> </span></span></li><li class="alt"><span> <span class="keyword">return</span><span> NULL; </span></span></li><li><span>} </span></li><li class="alt"><span><span class="keyword">if</span><span> ((*varval)->is_ref || (*varval)->refcount < 2) { </span></span></li><li><span> <span class="comment">/* varname is the only actual reference,</span> </span></li><li class="alt"><span><span class="comment"> * or it's a full reference to other variables</span> </span></li><li><span><span class="comment"> * either way: no separating to be done</span> </span></li><li class="alt"><span><span class="comment"> */</span><span> </span></span></li><li><span> <span class="keyword">return</span><span> *varval; </span></span></li><li class="alt"><span>} </span></li><li><span><span class="comment">/* Otherwise, make a copy of the zval* value */</span><span> </span></span></li><li class="alt"><span>MAKE_STD_ZVAL(varcopy); </span></li><li><span>varcopy = *varval; </span></li><li class="alt"><span><span class="comment">/* Duplicate any allocated structures within the zval* */</span><span> </span></span></li><li><span>zval_copy_ctor(varcopy); </span></li><li class="alt"><span> </span></li><li><span><span class="comment">/* Remove the old version of varname</span> </span></li><li class="alt"><span><span class="comment">* This will decrease the refcount of varval in the process</span> </span></li><li><span><span class="comment">*/</span><span> </span></span></li><li class="alt"><span>zend_hash_del(EG(active_symbol_table), varname, varname_len + 1); </span></li><li><span> </span></li><li class="alt"><span><span class="comment">/* Initialize the reference count of the</span> </span></li><li><span><span class="comment">* newly created value and attach it to</span> </span></li><li class="alt"><span><span class="comment">* the varname variable</span> </span></li><li><span><span class="comment">*/</span><span> </span></span></li><li class="alt"><span>varcopy->refcount = 1; </span></li><li><span>varcopy->is_ref = 0; </span></li><li class="alt"><span>zend_hash_add(EG(active_symbol_table), varname, varname_len + 1, </span></li><li><span> &varcopy, sizeof(zval*), NULL); </span></li><li class="alt"><span><span class="comment">/* Return the new zval* */</span><span> </span></span></li><li><span><span class="keyword">return</span><span> varcopy; </span></span></li><li class="alt"><span>} </span></li></ol>
Copy after login
-
写时改变
<ol class="dp-c"><li class="alt"><span><span><?php </span></span></li><li><span> <span class="vars">$a</span><span> = 1; </span></span></li><li class="alt"><span> <span class="vars">$b</span><span> = &</span><span class="vars">$a</span><span>; </span></span></li><li><span> <span class="vars">$b</span><span> += 5; </span></span></li><li class="alt"><span>?> </span></li></ol>
上面这段代码执行完之后一般希望是:$a == $b == 1
。这个又是怎么实现的呢?
-
$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引用这个时候怎么办?<span class="php"><span class="hljs-preprocessor"><br type="_moz" />
</span></span>
<ol class="dp-c"><li class="alt"><span><span><?php </span></span></li><li><span> <span class="vars">$a</span><span> = 1; </span></span></li><li class="alt"><span> <span class="vars">$b</span><span> = </span><span class="vars">$a</span><span>; </span></span></li><li><span> <span class="vars">$c</span><span> = &</span><span class="vars">$a</span><span>; </span></span></li><li class="alt"><span>?> </span></li></ol>
如果出现上面这种情况的时候,如果$a、$b、$c
指向同一个zval结构体,进行改变的时候Zend到底去听谁的?其实这个地方不会指向同一个zval了。
如果对一个is_ref = 0 && refcount >1
的zval进行写时改变这种赋值形式(就是引用赋值)的时候,Zend会将等号右边的变量分离出来一个新的zval,
对这个zval进行初始化,对之前的zval的refcount进行减1操作,让等号左边的变量指向这个新的zval,refcount进行加1操作,is_ref=1。看看下面这张图片
<span class="php"><span class="hljs-preprocessor"><br type="_moz" />
</span></span>
<ol class="dp-c"><li class="alt"><span><span><?php </span></span></li><li><span> <span class="vars">$a</span><span> = 1; </span></span></li><li class="alt"><span> <span class="vars">$b</span><span> = &</span><span class="vars">$a</span><span>; </span></span></li><li><span> <span class="vars">$c</span><span> = </span><span class="vars">$a</span><span>; </span></span></li><li class="alt"><span>?> </span></li></ol>
上面这又是另外一种情况,在is_ref = 1
的情况下,试图单纯的进行refcount+1操作的时候会分离出来一个新的zval给等号左边的变量,并初始化他,看看下面这张图片
参考文献
1.《Extending and Embedding PHP》- Chaper 3 - Memory Management.

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics



PHP 8.4 brings several new features, security improvements, and performance improvements with healthy amounts of feature deprecations and removals. This guide explains how to install PHP 8.4 or upgrade to PHP 8.4 on Ubuntu, Debian, or their derivati

Visual Studio Code, also known as VS Code, is a free source code editor — or integrated development environment (IDE) — available for all major operating systems. With a large collection of extensions for many programming languages, VS Code can be c

If you are an experienced PHP developer, you might have the feeling that you’ve been there and done that already.You have developed a significant number of applications, debugged millions of lines of code, and tweaked a bunch of scripts to achieve op

This tutorial demonstrates how to efficiently process XML documents using PHP. XML (eXtensible Markup Language) is a versatile text-based markup language designed for both human readability and machine parsing. It's commonly used for data storage an

JWT is an open standard based on JSON, used to securely transmit information between parties, mainly for identity authentication and information exchange. 1. JWT consists of three parts: Header, Payload and Signature. 2. The working principle of JWT includes three steps: generating JWT, verifying JWT and parsing Payload. 3. When using JWT for authentication in PHP, JWT can be generated and verified, and user role and permission information can be included in advanced usage. 4. Common errors include signature verification failure, token expiration, and payload oversized. Debugging skills include using debugging tools and logging. 5. Performance optimization and best practices include using appropriate signature algorithms, setting validity periods reasonably,

A string is a sequence of characters, including letters, numbers, and symbols. This tutorial will learn how to calculate the number of vowels in a given string in PHP using different methods. The vowels in English are a, e, i, o, u, and they can be uppercase or lowercase. What is a vowel? Vowels are alphabetic characters that represent a specific pronunciation. There are five vowels in English, including uppercase and lowercase: a, e, i, o, u Example 1 Input: String = "Tutorialspoint" Output: 6 explain The vowels in the string "Tutorialspoint" are u, o, i, a, o, i. There are 6 yuan in total

Static binding (static::) implements late static binding (LSB) in PHP, allowing calling classes to be referenced in static contexts rather than defining classes. 1) The parsing process is performed at runtime, 2) Look up the call class in the inheritance relationship, 3) It may bring performance overhead.

What are the magic methods of PHP? PHP's magic methods include: 1.\_\_construct, used to initialize objects; 2.\_\_destruct, used to clean up resources; 3.\_\_call, handle non-existent method calls; 4.\_\_get, implement dynamic attribute access; 5.\_\_set, implement dynamic attribute settings. These methods are automatically called in certain situations, improving code flexibility and efficiency.
