搜索
首页后端开发php教程变量改变时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);这句代码执行后,内核会将azvalrefcountb还和原来一样

写时复制

<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>

写时改变

<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。看看下面这张图片

变量改变时PHP内核做了些什么?_PHP教程

<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给等号左边的变量,并初始化他,看看下面这张图片

变量改变时PHP内核做了些什么?_PHP教程

参考文献

1.《Extending and Embedding PHP》- Chaper 3 - Memory Management.


www.bkjia.comtruehttp://www.bkjia.com/PHPjc/1056558.htmlTechArticle变量改变时PHP内核做了些什么? 引言 内容来自于《Extending and Embedding PHP》- Chaper 3 - Memory Management,加上自己的理解,对php中变量的引用计...
声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
PHP Notice: Undefined variable:解决方法PHP Notice: Undefined variable:解决方法Jun 25, 2023 pm 04:18 PM

在PHP开发中,我们经常会遇到PHPNotice:Undefinedvariable的错误提示。这个错误提示表示我们在代码中使用了一个未定义的变量。虽然这个错误提示不会导致代码崩溃,但是它会影响代码的可读性和可维护性。下面,本文将为大家介绍一些解决这个错误的方法。1.在开发过程中使用error_reporting(E_ALL)函数在PHP开发中,我们可

PHP Notice: Undefined variable: arr in的解决方法PHP Notice: Undefined variable: arr in的解决方法Jun 22, 2023 am 10:21 AM

PHPNotice:Undefinedvariable:arrin的解决方法在PHP编程中,我们经常会遇到“Notice:Undefinedvariable”这个错误提示。这个错误提示一般是因为访问了未定义的变量或者变量未被初始化导致的。对于这个问题,我们需要及时找到问题并解决。在本文中,我们将重点讨论PHPNotice:Undefin

PHP Notice: Undefined variable: sql的解决方法PHP Notice: Undefined variable: sql的解决方法Jun 23, 2023 am 08:51 AM

在开发PHP应用程序时,如果遇到了"Undefinedvariable:sql"的提示,这通常意味着您正在引用一个未定义的变量。这可能是由于许多原因引起的,例如变量名称拼写错误、作用域问题或代码中的语法错误等。在本篇文章中,我们将探讨这个问题的各种原因,并提供一些解决这个问题的方法。1.变量名称拼写错误在您的PHP代码中,如果变量名称不正确或拼写错误,系

PHP Notice: Undefined variable: result的解决方法PHP Notice: Undefined variable: result的解决方法Jun 22, 2023 pm 01:32 PM

PHPNotice:Undefinedvariable:result是指在PHP程序中调用了一个未定义的变量result,这会导致程序产生Notice级别的警告。这种情况一般是由于程序员在编写PHP代码时未正确定义变量或者变量的作用域造成的。如果不及时解决,这种Notice级别的警告可能会导致程序的运行出现问题。那么,如何解决PHPNotice:

如何通过引用传递PHP变量如何通过引用传递PHP变量Aug 26, 2023 am 09:01 AM

在PHP中,您可以使用和号(&)符号将变量按引用而不是按值传递。这样可以在函数或方法内修改原始变量。主要有两种方式可以通过引用传递PHP变量:使用ampersand符号在函数/方法声明中使用和符号将变量传递给函数/方法时在函数/方法声明中使用和号在PHP中,您可以使用函数/方法声明中的和号符号(&)通过引用传递变量。以下是更新的解释:要通过在函数/方法声明中使用&符号来传递引用变量,您需要在函数/方法定义中在参数名称之前包含&符号。这表示参数应该通过引用传递,允许

PHP7底层开发原理入门指南:从零开始学习PHP内核的奥秘PHP7底层开发原理入门指南:从零开始学习PHP内核的奥秘Sep 08, 2023 pm 04:34 PM

PHP7底层开发原理入门指南:从零开始学习PHP内核的奥秘引言:随着互联网的迅猛发展,PHP作为一种流行的服务器端脚本语言,具备了广泛的应用场景。然而,很多人对于PHP的内部原理和工作原理却知之甚少。对于想要深入了解PHP内核的开发者来说,本文将提供一个入门指南,帮助他们从零开始学习PHP内核的奥秘。一、PHP内核的基本概念PHP的编译过程在PHP的编译过程

如何在PHP中使用数字变量如何在PHP中使用数字变量Sep 13, 2023 pm 12:46 PM

如何在PHP中使用数字变量在PHP中,数字变量是一种无需声明而直接使用的变量类型。可以使用数字变量进行数学计算、数据比较和其他数值操作。本文将介绍如何在PHP中使用数字变量,并提供具体的代码示例。定义数字变量在PHP中,定义数字变量非常简单,只需直接给变量赋予一个数字即可。下面是一个例子:$number=10;在上面的代码中,我们定义了一个名为$numb

PHP7底层开发原理详细介绍:了解PHP内核架构和组件的关系PHP7底层开发原理详细介绍:了解PHP内核架构和组件的关系Sep 08, 2023 am 10:51 AM

PHP7底层开发原理详细介绍:了解PHP内核架构和组件的关系PHP是一种广泛使用的服务器端脚本语言,它可以嵌入到HTML中,用于开发动态网页。PHP的优势在于它简单易学,且与多种数据库集成得很好。对于很多开发者来说,他们都希望深入了解PHP底层的开发原理,以便更好地利用PHP提供的强大功能。本文将详细介绍PHP7底层开发原理,帮助读者深入理解PHP的内核架构

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前By尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
1 个月前By尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
4 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器