Heim >Backend-Entwicklung >PHP-Tutorial >变量改变时PHP内核做了些什么?_PHP教程

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

WBOY
WBOYOriginal
2016-07-12 09:08:19924Durchsuche

变量改变时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中变量的引用计...
Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn