常量,顾名思义是一个常态的量值。它与值只绑定一次,它的作用在于有肋于增加程序的可读性和可靠性。 在PHP中,常量的名字是一个简单值的标识符,在脚本执行期间该值不能改变。 和变量一样,常量默认为大小写敏感,但是按照我们的习惯常量标识符总是大写的。 常量名和其它任何 PHP 标签遵循同样的命名规则。合法的常量名以字母或下划线开始,后面跟着任何字母,数字或下划线。
- 在设定以后,常量的值无法更改
- 常量名不需要开头的美元符号 ($)
- 作用域不影响对常量的访问
- 常量值只能是字符串或数字
在这一小节我们一起看下常量与我们常见的变量有啥区别,它在执行期间的不可改变的特性是如何实现的以及常量的定义过程。
首先看下常量与变量的区别,常量是在变量的zval结构的基础上添加了一额外的元素。如下所示为PHP中常量的内部结构。
常量的内部结构
typedef struct _zend_constant { zval value; /* zval结构,PHP内部变量的存储结构,在第一小节有说明 */ int flags; /* 常量的标记如 CONST_PERSISTENT | CONST_CS */ char *name; /* 常量名称 */ uint name_len; int module_number; /* 模块号 */ } zend_constant;
在Zend/zend_constants.h文件的33行可以看到如上所示的结构定义。 在常量的结构中,除了与变量一样的zval结构,它还包括属于常量的标记,常量名以及常量所在的模块号。
在了解了常量的存储结构后,我们来看PHP常量的定义过程。一个例子。
define('NOWAMAGIC', 'www.nowamagic.net');
这是一个很常规的常量定义过程,它使用了PHP的内置函数define。常量名为NOWAMAGIC,值为一个字符串,存放在zval结构中。 从这个例子出发,我们看下define定义常量的过程实现。
define定义常量
define是PHP的内置函数,在Zend/zend_builtin_functions.c文件中定义了此函数的实现。如下所示为部分源码:
/* {{{ proto bool define(string constant_name, mixed value, boolean case_insensitive=false) Define a new constant */ ZEND_FUNCTION(define) { if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|b", &name, &name_len, &val, &non_cs) == FAILURE) { return; } ... // 类常量定义 此处不做介绍 ... // 值类型判断和处理 c.value = *val; zval_copy_ctor(&c.value); if (val_free) { zval_ptr_dtor(&val_free); } c.flags = case_sensitive; /* non persistent */ c.name = zend_strndup(name, name_len); c.name_len = name_len+1; c.module_number = PHP_USER_CONSTANT; if (zend_register_constant(&c TSRMLS_CC) == SUCCESS) { RETURN_TRUE; } else { RETURN_FALSE; } } /* }}} */
上面的代码已经对对象和类常量做了简化处理, 其实现基本上是一个将传递的参数传递给新建的zend_constant结构,并将这个结构体注册到常量列表中的过程。 关于大小写敏感,函数的第三个参数表示是否大小不敏感,默认为false(大小写敏感)。这个参数最后会赋值给zend_constant结构体的flags字段。其在函数中实现代码如下:
zend_bool non_cs = 0; // 第三个参数的临时存储变量 int case_sensitive = CONST_CS; // 是否大小写敏感,默认为1 if(non_cs) { // 输入为真,大小写不敏感 case_sensitive = 0; } c.flags = case_sensitive; // 赋值给结构体字段
从上面的define函数的实现来看,PHP对于常量的名称在定义时其实是没有所谓的限制。如下所示代码:
define('^_^', 'smile'); if (defined('^_^')) { echo 'yes'; }else{ echo 'no'; } //$var = ^_^; //语法错误 $var = constant("^_^");
通过defined函数测试表示,‘^_^’这个常量已经定义好,这样的常量无法直接调用, 只能使用constant语句来使用, 否则在语法解析时会显示错误。 在上面的代码中有用到一个判断常量是否定义的函数,下面我们看看这个函数是如何实现的。
判断常量是否设置
和define一样, defined的实现也在Zend/zend_builtin_functions.c文件, 其实现是一个读取参数变量,调用 zend_get_constant_ex函数获取常量的值来判断常量是否存在的过程。 而zend_get_constant_ex函数不仅包括了常规的常规的常量获取,还包括类常量的获取, 最后是通过zend_get_constant函数获取常量的值。在zend_get_constant函数中,基本上是通过下面的代码来获取常量的值。
zend_hash_find(EG(zend_constants), name, name_len+1, (void **) &c)
除此之外,只是调用这个函数之前和之后对name有一些特殊的处理。
常量的初始化
以上通过define定义的常量的模块编号都是PHP_USER_CONSTANT,这表示是用户定义的常量。 除此之外我们在平时使用较多的,如在显示所有级别错误报告时使用的E_ALL常量就有点不同了。 这里我们以cgi模式为例说明标准常量的定义过程。 整个调用顺序如下所示:
[php_cgi_startup() -> php_module_startup() -> zend_startup() -> zend_register_standard_constants()]
void zend_register_standard_constants(TSRMLS_D) { ... // 若干常量以REGISTER_MAIN_LONG_CONSTANT设置, REGISTER_MAIN_LONG_CONSTANT("E_ALL", E_ALL, CONST_PERSISTENT | CONST_CS); ... }
REGISTER_MAIN_LONG_CONSTANT宏展开是以zend_register_long_constant实现。 zend_register_long_constant函数将常量中值的类型,值,名称及模块号赋值给新的zend_constant。 并调用zend_register_constant添加到全局的常量列表中。
[php_cgi_startup() -> php_module_startup() -> zend_startup() -> zend_register_standard_constants() -> zend_register_constant]
ZEND_API void zend_register_long_constant(const char *name, uint name_len, long lval, int flags, int module_number TSRMLS_DC) { zend_constant c; c.value.type = IS_LONG; c.value.value.lval = lval; c.flags = flags; c.name = zend_strndup(name, name_len-1); c.name_len = name_len; c.module_number = module_number; zend_register_constant(&c TSRMLS_CC); }
zend_register_constant函数首先根据常量中的c->flags判断是否区分大小写, 如果不区分,则名字统一为小写,如果包含"\\",也统一成小写。否则为定义的名字 然后将调用下面的语句将当前常量添加到EG(zend_constants)。 EG(zend_constants)是一个HashTable(这在前面的章节中说明), 下面的代码是将常量添加到这个HashTable中。
zend_hash_add(EG(zend_constants), name, c->name_len, (void *) c, sizeof(zend_constant), NULL)==FAILURE)
在php_module_startup函数中,除了zend_startup函数中有注册标准的常量, 它本身体通过宏REGISTER_MAIN_LONG_CONSTANT等注册了一些常量,如:PHP_VERSION,PHP_OS等。
延伸阅读
此文章所在专题列表如下:
- 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内核探索:新垃圾回收机制说明
本文地址:http://www.nowamagic.net/librarys/veda/detail/1368,欢迎访问原出处。
大家都在看