变量改变时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>
Nach dem Login kopieren
-
-
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>
Nach dem Login kopieren
-
写时改变
<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.

Heiße KI -Werkzeuge

Undresser.AI Undress
KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover
Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool
Ausziehbilder kostenlos

Clothoff.io
KI-Kleiderentferner

AI Hentai Generator
Erstellen Sie kostenlos Ai Hentai.

Heißer Artikel

Heiße Werkzeuge

Notepad++7.3.1
Einfach zu bedienender und kostenloser Code-Editor

SublimeText3 chinesische Version
Chinesische Version, sehr einfach zu bedienen

Senden Sie Studio 13.0.1
Leistungsstarke integrierte PHP-Entwicklungsumgebung

Dreamweaver CS6
Visuelle Webentwicklungstools

SublimeText3 Mac-Version
Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

Heiße Themen



PHP 8.4 bringt mehrere neue Funktionen, Sicherheitsverbesserungen und Leistungsverbesserungen mit einer beträchtlichen Menge an veralteten und entfernten Funktionen. In dieser Anleitung wird erklärt, wie Sie PHP 8.4 installieren oder auf PHP 8.4 auf Ubuntu, Debian oder deren Derivaten aktualisieren. Obwohl es möglich ist, PHP aus dem Quellcode zu kompilieren, ist die Installation aus einem APT-Repository wie unten erläutert oft schneller und sicherer, da diese Repositorys in Zukunft die neuesten Fehlerbehebungen und Sicherheitsupdates bereitstellen.

Um in cakephp4 mit Datum und Uhrzeit zu arbeiten, verwenden wir die verfügbare FrozenTime-Klasse.

CakePHP ist ein Open-Source-Framework für PHP. Es soll die Entwicklung, Bereitstellung und Wartung von Anwendungen erheblich vereinfachen. CakePHP basiert auf einer MVC-ähnlichen Architektur, die sowohl leistungsstark als auch leicht zu verstehen ist. Modelle, Ansichten und Controller gu

Um am Datei-Upload zu arbeiten, verwenden wir den Formular-Helfer. Hier ist ein Beispiel für den Datei-Upload.

Der Validator kann durch Hinzufügen der folgenden zwei Zeilen im Controller erstellt werden.

Die Anmeldung bei CakePHP ist eine sehr einfache Aufgabe. Sie müssen nur eine Funktion verwenden. Sie können Fehler, Ausnahmen, Benutzeraktivitäten und von Benutzern durchgeführte Aktionen für jeden Hintergrundprozess wie Cronjob protokollieren. Das Protokollieren von Daten in CakePHP ist einfach. Die Funktion log() wird bereitgestellt

Visual Studio Code, auch bekannt als VS Code, ist ein kostenloser Quellcode-Editor – oder eine integrierte Entwicklungsumgebung (IDE) –, die für alle gängigen Betriebssysteme verfügbar ist. Mit einer großen Sammlung von Erweiterungen für viele Programmiersprachen kann VS Code c

CakePHP ist ein Open-Source-MVC-Framework. Es erleichtert die Entwicklung, Bereitstellung und Wartung von Anwendungen erheblich. CakePHP verfügt über eine Reihe von Bibliotheken, um die Überlastung der häufigsten Aufgaben zu reduzieren.
