搜索
首页后端开发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等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。