搜尋
首頁後端開發php教程【译】PHP的变量实现(给PHP开发者的PHP源码-第三一部分)

【译】PHP的变量实现(给PHP开发者的PHP源码-第三部分)

文章来自:http://www.aintnot.com/2016/02/12/phps-source-code-for-php-developers-part3-variables-ch

原文:http://blog.ircmaxell.com/2012/03/phps-source-code-for-php-developers_21.html

在"给PHP开发者的PHP源码"系列的第三篇文章,我们打算扩展上一篇文章来帮助理解PHP内部是怎么工作的。在第一篇文章,我们介绍了如何查看PHP的源码,它的代码结构是怎样的以及一些介绍给PHP开发者的C指针基础。第二篇文章介绍了函数。这一次,我们打算深入PHP最有用的结构之一:变量。

进入ZVAL

在PHP的核心代码中,变量被称为ZVAL。这个结构之所以那么重要是有原因的,不仅仅是因为PHP使用弱类型而C使用强类型。那么ZVAL是怎么解决这个问题的呢?要回答这个问题,我们需要认真的查看ZVAL类型的定义。要查看这个定义,让我们尝试在lxr页面的定义搜索框里搜索zval。乍一眼看去,我们似乎找不到任何有用的东西。但是有一行typedef在zend.h文件(typedef在C里面是一种定义新的数据类型的方式)。这个也许就是我们要找的东西,再继续查看。原来,这看起来是不相干的。这里并没有任何有用的东西。但为了确认一些,我们来点击_zval_struct这一行。

<span style="color: #008080;">1</span> <span style="color: #0000ff;">struct</span><span style="color: #000000;"> _zval_struct {</span><span style="color: #008080;">2</span> <span style="color: #008000;">/*</span><span style="color: #008000;"> Variable information </span><span style="color: #008000;">*/</span><span style="color: #008080;">3</span> zvalue_value value; <span style="color: #008000;">/*</span><span style="color: #008000;"> value </span><span style="color: #008000;">*/</span><span style="color: #008080;">4</span> <span style="color: #000000;">zend_uint refcount__gc;</span><span style="color: #008080;">5</span> zend_uchar type; <span style="color: #008000;">/*</span><span style="color: #008000;"> active type </span><span style="color: #008000;">*/</span><span style="color: #008080;">6</span> <span style="color: #000000;">zend_uchar is_ref__gc;</span><span style="color: #008080;">7</span> };

然后我们就得到PHP的基础,zval。看起来很简单,对吗?是的,没错,但这里还有一些很有意义的神奇的东西。注意,这是一个结构或结构体。基本上,这可以看作PHP里面的类,这些类只有公共的属性。这里,我们有四个属性:value,refcount__gc,type以及is_ref__gc。让我们来一一查看这些属性(省略它们的顺序)。

Value

我们第一个谈论的元素是value变量,它的类型是zvalue_value。我不认识你,但我也从来没有听说过zvalue_value。那么让我们尝试弄懂它是什么。跟网站的其他部分一样,你可以点击某个类型查看它的定义。一旦你点击了,你会看到它的定义跟下面的是一样的:

<span style="color: #000000;">typedef union _zvalue_value {    </span><span style="color: #0000ff;">long</span> lval; <span style="color: #008000;">/*</span><span style="color: #008000;"> long value </span><span style="color: #008000;">*/</span>    <span style="color: #0000ff;">double</span> dval; <span style="color: #008000;">/*</span><span style="color: #008000;"> double value </span><span style="color: #008000;">*/</span>    <span style="color: #0000ff;">struct</span><span style="color: #000000;"> {        </span><span style="color: #0000ff;">char</span> *<span style="color: #000000;">val;        </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> len;    } str;    HashTable </span>*ht; <span style="color: #008000;">/*</span><span style="color: #008000;"> hash table value </span><span style="color: #008000;">*/</span><span style="color: #000000;">    zend_object_value obj;} zvalue_value;</span>

现在,这里有一些黑科技。看到那个union的定义吗?那意味着这不是真正的结构体,而是一个单独的类型。但是有多个类型的变量在里面。如果这里面有多种类型的话,那它怎么能作为单一的类型呢?我很高兴你问了这个问题。要理解这个问题,我们需要先回想我们在第一篇文章谈论的C语言中的类型。

在C里面,变量只是一行内存地址的标签。也可以说类型只是标识哪一块内存将被使用的方式。在C里面没有使用任何东西将4个字节的字符串和整型值分隔开。它们都只是一整块的内存。编译器会尝试通过"标识"内存段作为变量来解析它,然后将这些变量转换为特定的类型,但这并不是总是成功(顺便说一句,当一个变量“重写”它得到的内存段,那将会产生段错误)。

那么,据我们所知,union是单独的类型,它根据怎么被访问而使用不同的方式解释。这可以让我们定义一个值来支持多种类型。有一点要注意的是,所有类型的数据都必须使用同一块内存来存储。这个例子,在64位的编译器,long和double都会占用64个位来保存。字符串结构体会占用96位(64位存储字符指针,32位保存整型长度)。hash_table会占用64位,还有zend_object_value会占用96位(32位用来存储元素,剩下的64位来存储指针)。而整一个union会占用最大元素的内存大小,因此在这里就是96位。

现在,如果再看清楚这个联合体(union),我们可以看到只有5种PHP数据类型在这里(long == int,double == float,str == string,hashtable == array,zend_object_value == object)。那么剩下的数据类型去了哪里呢?原来,这个结构体已经足够来存储剩余的数据类型。BOOL使用long(int)来存储,NULL不占用数据段,RESOURCE也使用long来存储。

TYPE

因为这个value联合体并没有控制它是怎么被访问的,我们需要其他方式来记录变量的类型。这里,我们可以通过数据类型来得出如何访问value的信息。它使用type这个字节来处理这个问题(zend_uchar是一个无符号的字符,或者内存中的一个字节)。它从zend类型常量保留这些信息。这真的是一种魔法,是需要使用zval.type = IS_LONG来定义整型数据。因此这个字段和value字段就足够让我们知道PHP变量的类型和值。

IS_REF

这个字段标识变量是否为引用。那就是说,如果你执行了在变量里执行了$foo = &$bar。如果它是0,那么变量就不是一个引用,如果它是1,那么变量就是一个引用。它并没有做太多的事情。那么,在我们结束_zval_struct之前,再看一看它的第四个成员。

REFCOUNT

这个变量是指向PHP变量容器的指针的计数器。也就是说,如果refcount是1,那就表示有一个PHP变量使用这个容器。如果refcount是2,那就表示有两个PHP变量指向同一个变量容器。单独的refcount变量并没有太多有用的信息,但如果它与is_ref一起使用,就构成了垃圾回收器和写时复制的基础。它允许我们使用同一个zval容器来保存一个或多个PHP变量。refcount的语义解释超出这篇文章的范围,如果你想继续深入,我推荐你查看这篇文档。

这就是ZVAL的所有内容。

它是怎么工作的?

在PHP内部,zval使用跟其他C变量一样,作为内存段或者一个指向内存段的指针(或者指向指针的指针,等等),传递到函数。一旦我们有了变量,我们就想访问它里面的数据。那我们要怎么做到呢?我们使用定义在zend_operators.h文件里面的宏来跟zval一起使用,使得访问数据更简单。有一点很重要的是,每一个宏都有多个拷贝。不同的是它们的前缀。例如,要得出zval的类型,有Z_TYPE(zval)宏,这个宏返回一个整型数据来表示zval参数。但这里还有一个Z_TYPE(zval_p)宏,它跟Z_TYPE(zval)做的事情是一样的,但它返回的是指向zval的指针。事实上,除了参数的属性不一样之外,这两个函数是一样的,实际上,我们可以使用Z_TYPE(*zval_p),但_P和_PP让事情更简单。

我们可以使用VAL这一类宏来获取zval的值。可以调用Z_LVAL(zval)来得到整型值(比如整型数据和资源数据)。调用Z_DVAL(zval)来得到浮点值。还有很多其他的,到这里到此为止。要注意的关键是,为了在C里面获取zval的值,你需要使用宏(或应该)。因此,当我们看见有函数使用它们时,我们就知道它是从zval里面提取它的值。

那么,类型呢?

到现在为止,我们知识谈论了类型和zval的值。我们都知道,PHP帮我们做了类型判断。因此,如果我们喜欢,我们可以将一个字符串当作一个整型值。我们把这一步叫做convert_to_type。要转换一个zval为string值,就调用convert_to_string函数。它会改变我们传递给函数的ZVAL的类型。因此,如果你看到有函数在调用这些函数,你就知道它是在转换参数的数据类型。

Zend_Parse_Paramenters

上一篇文章中,介绍了zend_parse_paramenters这个函数。既然我们知道PHP变量在C里面是怎么表示的,那我们就来深入看看。

ZEND_API <span style="color: #0000ff;">int</span> zend_parse_parameters(<span style="color: #0000ff;">int</span> num_args TSRMLS_DC, <span style="color: #0000ff;">const</span> <span style="color: #0000ff;">char</span> *<span style="color: #000000;">type_spec, ...){    va_list va;    </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> retval;    RETURN_IF_ZERO_ARGS(num_args, type_spec, </span><span style="color: #800080;">0</span><span style="color: #000000;">);    va_start(va, type_spec);    retval </span>= zend_parse_va_args(num_args, type_spec, &va, <span style="color: #800080;">0</span><span style="color: #000000;"> TSRMLS_CC);    va_end(va);    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> retval;}</span>

现在,从表面上看,这看起来很迷惑。重点要理解的是,va_list类型只是一个使用'...'的可变参数列表。因此,它跟PHP中的func_get_args()函数的构造差不多。有了这个东西,我们可以看到zend_parse_parameters函数马上调用zend_parse_va_args函数。我们继续往下看看这个函数...

这个函数看起来很有趣。第一眼看去,它好像做了很多事情。但仔细看看。首先,我们可以看到一个for循环。这个for循环主要遍历从zend_parse_parameters传递过来的type_spec字符串。在循环里面我们可以看到它只是计算期望接收到的参数数量。它是如何做到这些的研究就留给读者。

继续往下看,我么可以看到有一些合理的检查(检查参数是否都正确地传递),还有错误检查,检查是否传递了足够数量的参数。接下来进入一个我们感兴趣的循环。这个循环真正解析那些参数。在循环里面,我们可以看到有三个if语句。第一个处理可选参数的标识符。第二个处理var-args(参数的数量)。第三个if语句正是我们感兴趣的。可以看到,这里调用了zend_parse_arg()函数。让我们再深入看看这个函数...

继续往下看,我们可以看到这里有一些非常有趣的事情。这个函数再调用另一个函数(zend_parse_arg_impl),然后得到一些错误信息。这在PHP里面是一种很常见的模式,将函数的错误处理工作提取到父函数。这样代码实现和错误处理就分开了,而且可以最大化地重用。你可以继续深入研究那个函数,非常容易理解。但我们现在仔细看看zend_parse_arg_impl()...

现在,我们真正到了PHP内部函数解析参数的步骤。让我们看看第一个switch语句的分支,这个分支用来解析整型参数。接下来的应该很容易理解。那么,我们从分支的第一行开始吧:

<span style="color: #0000ff;">long</span> *p = va_arg(*va, <span style="color: #0000ff;">long</span> *);

如果你记得我们之前说的,va_args是C语言处理变量参数的方式。所以这里是定义一个整型指针(long在C里面是整型)。总之,它从va_arg函数里面得到指针。这说明,它得到传递给zend_parse_parameters函数的参数的指针。所以这就是我们会用分支结束后的值赋值的指针结果。接下来,我们可以看到进入一个根据传递进来的变量(zval)类型的分支。我们先看看IS_STRING分支(这一步会在传递整型值到字符串变量时执行)。

<span style="color: #0000ff;">case</span><span style="color: #000000;"> IS_STRING:{    </span><span style="color: #0000ff;">double</span><span style="color: #000000;"> d;    </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> type;    </span><span style="color: #0000ff;">if</span> ((type = is_numeric_string(Z_STRVAL_PP(arg), Z_STRLEN_PP(arg), p, &d, -<span style="color: #800080;">1</span>)) == <span style="color: #800080;">0</span><span style="color: #000000;">) {        </span><span style="color: #0000ff;">return</span> <span style="color: #800000;">"</span><span style="color: #800000;">long</span><span style="color: #800000;">"</span><span style="color: #000000;">;    } </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> (type ==<span style="color: #000000;"> IS_DOUBLE) {        </span><span style="color: #0000ff;">if</span> (c == <span style="color: #800000;">'</span><span style="color: #800000;">L</span><span style="color: #800000;">'</span><span style="color: #000000;">) {            </span><span style="color: #0000ff;">if</span> (d ><span style="color: #000000;"> LONG_MAX) {                </span>*p =<span style="color: #000000;"> LONG_MAX;                </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;            } </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> (d  LONG_MIN) {                *p =<span style="color: #000000;"> LONG_MIN;                </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;            }        }        </span>*p =<span style="color: #000000;"> zend_dval_to_lval(d);    }}</span><span style="color: #0000ff;">break</span>;

现在,这个做的事情并没有看起来的那么多。所有的事情都归结与is_numeric_string函数。总的来说,该函数检查字符串是否只包含整数字符,如果不是的话就返回0。如果是的话,它将该字符串解析到变量里(整型或浮点型,p或d),然后返回数据类型。所以我们可以看到,如果字符串不是纯数字,他返回“long”字符串。这个字符串用来包装错误处理函数。否则,如果字符串表示double(浮点型),它先检查这个浮点数作为整型数来存储的话是否太大,然后它使用zend_dval_to_lval函数来帮助解析浮点数到整型数。这就是我们所知道的。我们已经解析了我们的字符串参数。现在继续看看其他分支:

<span style="color: #0000ff;">case</span><span style="color: #000000;"> IS_DOUBLE:    </span><span style="color: #0000ff;">if</span> (c == <span style="color: #800000;">'</span><span style="color: #800000;">L</span><span style="color: #800000;">'</span><span style="color: #000000;">) {        </span><span style="color: #0000ff;">if</span> (Z_DVAL_PP(arg) ><span style="color: #000000;"> LONG_MAX) {            </span>*p =<span style="color: #000000;"> LONG_MAX;            </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;        } </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> (Z_DVAL_PP(arg)  LONG_MIN) {        *p =<span style="color: #000000;"> LONG_MIN;        </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;    }}</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> IS_NULL:</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> IS_LONG:</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> IS_BOOL:convert_to_long_ex(arg);</span>*p =<span style="color: #000000;"> Z_LVAL_PP(arg);</span><span style="color: #0000ff;">break</span>;

这里,我们可以看到解析浮点数的操作,这一步跟解析字符串里的浮点数相似(巧合?)。有一个很重要的事情要注意的是,如果参数的标识不是大写'L',它会跟其他类型变量一样的处理方式(这个case语句没有break)。现在,我们还有一个有趣的函数,convert_to_long_ex()。这跟我们之前说到的convert_to_type()函数集合是一类的,该函数转换参数为特定的类型。唯一的不同是,如果参数不是引用的话(因为这个函数在改变数据类型),这个函数就将变量的值及其引用分离(拷贝)了。( The only difference is that it separates (copies) the passed in variable if it's not a reference (since it's changing the type). )这就是写时复制的作用。因此,当我们传递一个浮点数到到一个非引用的整型变量,该函数会把它当作整型来处理,但我们仍然可以得到浮点型数据。

<span style="color: #0000ff;">case</span><span style="color: #000000;"> IS_ARRAY:</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> IS_OBJECT:</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> IS_RESOURCE:</span><span style="color: #0000ff;">default</span><span style="color: #000000;">:</span><span style="color: #0000ff;">return</span> <span style="color: #800000;">"</span><span style="color: #800000;">long</span><span style="color: #800000;">"</span>;

最后,我们还有另外三个case分支。我们可以看到,如果你传递一个数组、对象、资源或者其他不知道的类型到整型变量中,你会得到错误。

剩下的部分我们留给读者。阅读zend_parse_arg_impl函数对更好地理解额PHP类型判断系统真的很有用。一部分一部分地读,然后尽量追踪在C里面的各种参数的状态和类型。

下一部分

下一部分会在Nikic的博客(我们会在这个系列的文章来回跳转)。在下一篇,他会谈到数组的所有内容。

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
PHP的當前狀態:查看網絡開發趨勢PHP的當前狀態:查看網絡開發趨勢Apr 13, 2025 am 12:20 AM

PHP在現代Web開發中仍然重要,尤其在內容管理和電子商務平台。 1)PHP擁有豐富的生態系統和強大框架支持,如Laravel和Symfony。 2)性能優化可通過OPcache和Nginx實現。 3)PHP8.0引入JIT編譯器,提升性能。 4)雲原生應用通過Docker和Kubernetes部署,提高靈活性和可擴展性。

PHP與其他語言:比較PHP與其他語言:比較Apr 13, 2025 am 12:19 AM

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

PHP與Python:核心功能PHP與Python:核心功能Apr 13, 2025 am 12:16 AM

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

PHP:網絡開發的關鍵語言PHP:網絡開發的關鍵語言Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP:許多網站的基礎PHP:許多網站的基礎Apr 13, 2025 am 12:07 AM

PHP成為許多網站首選技術棧的原因包括其易用性、強大社區支持和廣泛應用。 1)易於學習和使用,適合初學者。 2)擁有龐大的開發者社區,資源豐富。 3)廣泛應用於WordPress、Drupal等平台。 4)與Web服務器緊密集成,簡化開發部署。

超越炒作:評估當今PHP的角色超越炒作:評估當今PHP的角色Apr 12, 2025 am 12:17 AM

PHP在現代編程中仍然是一個強大且廣泛使用的工具,尤其在web開發領域。 1)PHP易用且與數據庫集成無縫,是許多開發者的首選。 2)它支持動態內容生成和麵向對象編程,適合快速創建和維護網站。 3)PHP的性能可以通過緩存和優化數據庫查詢來提升,其廣泛的社區和豐富生態系統使其在當今技術棧中仍具重要地位。

PHP中的弱參考是什麼?什麼時候有用?PHP中的弱參考是什麼?什麼時候有用?Apr 12, 2025 am 12:13 AM

在PHP中,弱引用是通過WeakReference類實現的,不會阻止垃圾回收器回收對象。弱引用適用於緩存系統和事件監聽器等場景,需注意其不能保證對象存活,且垃圾回收可能延遲。

解釋PHP中的__ Invoke Magic方法。解釋PHP中的__ Invoke Magic方法。Apr 12, 2025 am 12:07 AM

\_\_invoke方法允許對象像函數一樣被調用。 1.定義\_\_invoke方法使對象可被調用。 2.使用$obj(...)語法時,PHP會執行\_\_invoke方法。 3.適用於日誌記錄和計算器等場景,提高代碼靈活性和可讀性。

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.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。