PHP内核探索:诡异的变量名
2011-06-07 00:00
2066 查看
在PHP语言中,变量都是保存在哈希表中,称为变量符号表,其中变量名为哈希表的键,变量名对应的容器zval的指针为哈希表中的值。所有全局变量放在一张主符号表中(也就是数组$GLOBALS对应的哈希表)。PHP语言有个特性,变量在命名时,$变量标识符后不能以数字开头。例如我们在以下代码:
会报如下错误:Parse error: syntax error, unexpected T_LNUMBER, expecting T_VARIABLE or '$' in...
从错误的描叙来看,这是一个语法错误,于是我们推论对变量名合法性的判断应该是在编译时的语法分析阶段。为了证明观点,我们可以试着在执行阶段定义一个数字字符开头的变量:
运行之后发现不报错,并且在全局符号表$GLOBALS中发现相关符号:
这样我们就定义了一个全数字字符命名的变量,似乎违背了PHP的规则,但是确实做到了。(读者可以试着输出$GLOBALS["111"]的值,虽然有值,但是结果却为NULL,这个是PHP中一个类型转换的特性)
在这段代码中,$$a = "nowamagic"这条语句具有多态性,只有当实际执行到这条语句的时候,我们才能确定变量名,PHP在语法分析阶段无法知道这个变量名会是什么,所以就不会报错,在执行阶段,PHP语言不判断变量名的合法性,于是就产生了这样一个叛逆的变量。
知道了这个特性之后,马上会想到一个另外一个特殊的变量:$this,在类的方法中,$this关键字用来指向当前类的对象实例,如果对$this进行赋值操作,会发生什么事情?
执行代码,报错:
PHP对$this变量做了一定保护措施,但是这个保护措施也不是完全的,我们利用前面的方法在执行期改变$this的值, 修改后的getName方法为:
然后执行代码,发现能够顺利执行,$this的值变成了123, 由此可以判断对$this关键字的保护也仅限与语法分析阶段。
当$this的值变成123之后,按理说$this->_name肯定会报错的,但是居然代码能正常执行,这个确实很让人觉得不可思议。于是推断
$this->_name这样的引用方式和一般的对象变量引用方式在有差异,我们继续对getName做修改:
这段代码中,$this和$b的值都等于123,但是$this->getAge()可以顺利执行,$b->getAge()却报错:Fatal error: Call to a member function getAge() on a non-object...
这是一个诡异的问题,于是可以推断$this->getAge()和$b->getAge()在编译之后对应的op handler肯定有所差异,于是查看之。
通过vld查看op的信息:
$this->getAge()对应的为: ZEND_INIT_METHOD_CALL RES[ IS_UNUSED ] OP1[ IS_UNUSED ] OP2[ IS_CONST (8142027) 'getAge' ]
$b->getAge()在对应的为: ZEND_INIT_METHOD_CALL RES[ IS_UNUSED ] OP1[ IS_CV !0 ] OP2[ , IS_CONST (8142039) 'getAge' ]
虽然对应了同样的op code,但是由于操作数的不同(前者不使用OP1,后者使用OP1, 且OP1的值!0表示$b),同一个op code对应同一类handler,然后根据操作数的类型确定到此类handler中的某一个handler。
$this->getAge()对应的handler处理为:ZEND_INIT_METHOD_CALL_SPEC_UNUSED_CONST_HANDLER
$b->getAge() 对应的handler处理为:ZEND_INIT_METHOD_CALL_SPEC_CV_CONST_HANDLER
这就是它们的差异了,在执行:
调用getName的时候,getName()的作用域(scope)已经被设置成$name对象中,语法分析的时候对于getName中$this->getAge()这样的调用时只是在当前作用域(scope)中调用getAge()函数,不会理会$this的具体值,而在$b->getAge()这样的调用时,会关心$b所对应的值。
PHP内核探索:从SAPI接口开始
PHP内核探索:一次请求的开始与结束
PHP内核探索:一次请求生命周期
PHP内核探索:单进程SAPI生命周期
PHP内核探索:多进程/线程的SAPI生命周期
PHP内核探索:Zend引擎
PHP内核探索:再次探讨SAPI
PHP内核探索:Apache模块介绍
PHP内核探索:通过mod_php5支持PHP
PHP内核探索:Apache运行与钩子函数
PHP内核探索:嵌入式PHP
PHP内核探索:PHP的FastCGI
PHP内核探索:如何执行PHP脚本
PHP内核探索:PHP脚本的执行细节
PHP内核探索:操作码OpCode
PHP内核探索:PHP里的opcode
PHP内核探索:解释器的执行过程
PHP内核探索:变量概述
PHP内核探索:变量存储与类型
PHP内核探索:PHP中的哈希表
PHP内核探索:理解Zend里的哈希表
PHP内核探索:PHP哈希算法设计
PHP内核探索:翻译一篇HashTables文章
PHP内核探索:哈希碰撞攻击是什么?
PHP内核探索:常量的实现
PHP内核探索:变量的存储
PHP内核探索:变量的类型
PHP内核探索:变量的值操作
PHP内核探索:变量的创建
PHP内核探索:预定义变量
PHP内核探索:变量的检索
PHP内核探索:变量的类型转换
PHP内核探索:弱类型变量的实现
PHP内核探索:静态变量的实现
PHP内核探索:变量类型提示
PHP内核探索:变量的生命周期
PHP内核探索:变量赋值与销毁
PHP内核探索:变量作用域
PHP内核探索:诡异的变量名
PHP内核探索:变量的value和type存储
PHP内核探索:全局变量Global
PHP内核探索:变量类型的转换
PHP内核探索:内存管理开篇
PHP内核探索:Zend内存管理器
PHP内核探索:PHP的内存管理
PHP内核探索:内存的申请与销毁
PHP内核探索:引用计数与写时复制
PHP内核探索:PHP5.3的垃圾回收机制
PHP内核探索:内存管理中的cache
PHP内核探索:写时复制COW机制
PHP内核探索:数组与链表
PHP内核探索:使用哈希表API
PHP内核探索:数组操作
PHP内核探索:数组源码分析
PHP内核探索:函数的分类
PHP内核探索:函数的内部结构
PHP内核探索:函数结构转换
PHP内核探索:定义函数的过程
PHP内核探索:函数的参数
PHP内核探索:zend_parse_parameters函数
PHP内核探索:函数返回值
PHP内核探索:形参return value
PHP内核探索:函数调用与执行
PHP内核探索:引用与函数执行
PHP内核探索:匿名函数及闭包
PHP内核探索:面向对象开篇
PHP内核探索:类的结构和实现
PHP内核探索:类的成员变量
PHP内核探索:类的成员方法
PHP内核探索:类的原型zend_class_entry
PHP内核探索:类的定义
PHP内核探索:访问控制
PHP内核探索:继承,多态与抽象类
PHP内核探索:魔术函数与延迟绑定
PHP内核探索:保留类与特殊类
PHP内核探索:对象
PHP内核探索:创建对象实例
PHP内核探索:对象属性读写
PHP内核探索:命名空间
PHP内核探索:定义接口
PHP内核探索:继承与实现接口
PHP内核探索:资源resource类型
PHP内核探索:Zend虚拟机
PHP内核探索:虚拟机的词法解析
PHP内核探索:虚拟机的语法分析
PHP内核探索:中间代码opcode的执行
PHP内核探索:代码的加密与解密
PHP内核探索:zend_execute的具体执行过程
PHP内核探索:变量的引用与计数规则
PHP内核探索:新垃圾回收机制说明
<?php $111= "nowamagic"; ?>
会报如下错误:Parse error: syntax error, unexpected T_LNUMBER, expecting T_VARIABLE or '$' in...
从错误的描叙来看,这是一个语法错误,于是我们推论对变量名合法性的判断应该是在编译时的语法分析阶段。为了证明观点,我们可以试着在执行阶段定义一个数字字符开头的变量:
<?php $a = 111; $$a = "nowamagic"; //以变量$a的值作为变量名 echo $$a; var_dump($GLOBALS); ?>
运行之后发现不报错,并且在全局符号表$GLOBALS中发现相关符号:
["a"]=> int(111) ["111"]=> string(2) "nowamagic"
这样我们就定义了一个全数字字符命名的变量,似乎违背了PHP的规则,但是确实做到了。(读者可以试着输出$GLOBALS["111"]的值,虽然有值,但是结果却为NULL,这个是PHP中一个类型转换的特性)
在这段代码中,$$a = "nowamagic"这条语句具有多态性,只有当实际执行到这条语句的时候,我们才能确定变量名,PHP在语法分析阶段无法知道这个变量名会是什么,所以就不会报错,在执行阶段,PHP语言不判断变量名的合法性,于是就产生了这样一个叛逆的变量。
知道了这个特性之后,马上会想到一个另外一个特殊的变量:$this,在类的方法中,$this关键字用来指向当前类的对象实例,如果对$this进行赋值操作,会发生什么事情?
<?php class Person { protected $_name = "phper"; protected $_age = 18; public function getName() { $this = 123; return $this->_name; } public function getAge() { return $this->_age; } } $p = new Person(); $p->getName(); ?>
执行代码,报错:
Fatal error: Cannot re-assign $this in...
PHP对$this变量做了一定保护措施,但是这个保护措施也不是完全的,我们利用前面的方法在执行期改变$this的值, 修改后的getName方法为:
public function getName() { $a = "this"; $$a = 123; echo $this; return $this->_name; }
然后执行代码,发现能够顺利执行,$this的值变成了123, 由此可以判断对$this关键字的保护也仅限与语法分析阶段。
当$this的值变成123之后,按理说$this->_name肯定会报错的,但是居然代码能正常执行,这个确实很让人觉得不可思议。于是推断
$this->_name这样的引用方式和一般的对象变量引用方式在有差异,我们继续对getName做修改:
public function getName() { $a = "this"; $$a = 123; $b = 123; echo $this; echo $b; $this->getAge(); $b->getAge(); return $this->_name; }
这段代码中,$this和$b的值都等于123,但是$this->getAge()可以顺利执行,$b->getAge()却报错:Fatal error: Call to a member function getAge() on a non-object...
这是一个诡异的问题,于是可以推断$this->getAge()和$b->getAge()在编译之后对应的op handler肯定有所差异,于是查看之。
通过vld查看op的信息:
$this->getAge()对应的为: ZEND_INIT_METHOD_CALL RES[ IS_UNUSED ] OP1[ IS_UNUSED ] OP2[ IS_CONST (8142027) 'getAge' ]
$b->getAge()在对应的为: ZEND_INIT_METHOD_CALL RES[ IS_UNUSED ] OP1[ IS_CV !0 ] OP2[ , IS_CONST (8142039) 'getAge' ]
虽然对应了同样的op code,但是由于操作数的不同(前者不使用OP1,后者使用OP1, 且OP1的值!0表示$b),同一个op code对应同一类handler,然后根据操作数的类型确定到此类handler中的某一个handler。
$this->getAge()对应的handler处理为:ZEND_INIT_METHOD_CALL_SPEC_UNUSED_CONST_HANDLER
$b->getAge() 对应的handler处理为:ZEND_INIT_METHOD_CALL_SPEC_CV_CONST_HANDLER
这就是它们的差异了,在执行:
$my = new Person(); $name = $my->getName();
调用getName的时候,getName()的作用域(scope)已经被设置成$name对象中,语法分析的时候对于getName中$this->getAge()这样的调用时只是在当前作用域(scope)中调用getAge()函数,不会理会$this的具体值,而在$b->getAge()这样的调用时,会关心$b所对应的值。
延伸阅读
此文章所在专题列表如下:PHP内核探索:从SAPI接口开始
PHP内核探索:一次请求的开始与结束
PHP内核探索:一次请求生命周期
PHP内核探索:单进程SAPI生命周期
PHP内核探索:多进程/线程的SAPI生命周期
PHP内核探索:Zend引擎
PHP内核探索:再次探讨SAPI
PHP内核探索:Apache模块介绍
PHP内核探索:通过mod_php5支持PHP
PHP内核探索:Apache运行与钩子函数
PHP内核探索:嵌入式PHP
PHP内核探索:PHP的FastCGI
PHP内核探索:如何执行PHP脚本
PHP内核探索:PHP脚本的执行细节
PHP内核探索:操作码OpCode
PHP内核探索:PHP里的opcode
PHP内核探索:解释器的执行过程
PHP内核探索:变量概述
PHP内核探索:变量存储与类型
PHP内核探索:PHP中的哈希表
PHP内核探索:理解Zend里的哈希表
PHP内核探索:PHP哈希算法设计
PHP内核探索:翻译一篇HashTables文章
PHP内核探索:哈希碰撞攻击是什么?
PHP内核探索:常量的实现
PHP内核探索:变量的存储
PHP内核探索:变量的类型
PHP内核探索:变量的值操作
PHP内核探索:变量的创建
PHP内核探索:预定义变量
PHP内核探索:变量的检索
PHP内核探索:变量的类型转换
PHP内核探索:弱类型变量的实现
PHP内核探索:静态变量的实现
PHP内核探索:变量类型提示
PHP内核探索:变量的生命周期
PHP内核探索:变量赋值与销毁
PHP内核探索:变量作用域
PHP内核探索:诡异的变量名
PHP内核探索:变量的value和type存储
PHP内核探索:全局变量Global
PHP内核探索:变量类型的转换
PHP内核探索:内存管理开篇
PHP内核探索:Zend内存管理器
PHP内核探索:PHP的内存管理
PHP内核探索:内存的申请与销毁
PHP内核探索:引用计数与写时复制
PHP内核探索:PHP5.3的垃圾回收机制
PHP内核探索:内存管理中的cache
PHP内核探索:写时复制COW机制
PHP内核探索:数组与链表
PHP内核探索:使用哈希表API
PHP内核探索:数组操作
PHP内核探索:数组源码分析
PHP内核探索:函数的分类
PHP内核探索:函数的内部结构
PHP内核探索:函数结构转换
PHP内核探索:定义函数的过程
PHP内核探索:函数的参数
PHP内核探索:zend_parse_parameters函数
PHP内核探索:函数返回值
PHP内核探索:形参return value
PHP内核探索:函数调用与执行
PHP内核探索:引用与函数执行
PHP内核探索:匿名函数及闭包
PHP内核探索:面向对象开篇
PHP内核探索:类的结构和实现
PHP内核探索:类的成员变量
PHP内核探索:类的成员方法
PHP内核探索:类的原型zend_class_entry
PHP内核探索:类的定义
PHP内核探索:访问控制
PHP内核探索:继承,多态与抽象类
PHP内核探索:魔术函数与延迟绑定
PHP内核探索:保留类与特殊类
PHP内核探索:对象
PHP内核探索:创建对象实例
PHP内核探索:对象属性读写
PHP内核探索:命名空间
PHP内核探索:定义接口
PHP内核探索:继承与实现接口
PHP内核探索:资源resource类型
PHP内核探索:Zend虚拟机
PHP内核探索:虚拟机的词法解析
PHP内核探索:虚拟机的语法分析
PHP内核探索:中间代码opcode的执行
PHP内核探索:代码的加密与解密
PHP内核探索:zend_execute的具体执行过程
PHP内核探索:变量的引用与计数规则
PHP内核探索:新垃圾回收机制说明
相关文章推荐
- [置顶] PHP内核探索之变量(7)- 不平凡的字符串
- PHP内核探索之变量
- PHP内核探索之变量(4)- 数组操作
- PHP内核探索之变量(6)- 后续内核探索系列大纲备忘
- PHP内核探索之变量(4)- 数组操作
- 19.PHP内核探索:变量存储与类型
- PHP内核探索之变量(1)变量的容器-Zval
- PHP内核探索之变量(6)- 后续内核探索系列大纲备忘
- PHP内核探索 —— 变量概述:变量在PHP的内部如何实现
- PHP内核探索之变量
- PHP内核探索之变量(5)- session的基本原理
- PHP内核探索之变量(5)- session的基本原理
- PHP内核探索之变量(6)- 后续内核探索系列大纲备忘
- PHP内核探索之变量(7)- 不平凡的字符串
- PHP内核探索 —— 变量存储与类型
- PHP内核探索之变量(2)-理解引用
- PHP内核探索之变量(4)- 数组操作
- PHP内核探索笔记-变量
- 探索PHP内核中变量的容器-Zval
- PHP内核探索:变量类型的转换