TIPI:深入理解PHP内核
常用代码
在PHP源码中经常会看到一些很常见的宏,这些代码在PHP的源码中出现的频率极高
- ‘##’ 和 ‘#’
宏是C/C++非常强大,使用也很多的一个功能,有时用来实现类似函数内联的效果,或者将复杂的代码进行简单封装,提高可读性或者可移植性。
在PHP的宏定义中经常使用双井号
双井号’##’:
在C语言的宏中,’##’被称为连接符,它是一种预处理运算符,用来把两个语言符号组合成单个语言符号。这里的语言符号不一定是宏的变量,并且双井号不能走位第一个或者最后一个元素存在。
1 |
|
宏ZEND_FN(name)中有一个’##’,它的作用一如之前所说,是一个连接符,将zif和宏的变量name的值连接起来,以这种连接的方式以基础,多次使用这种宏形式,可以将它作为一个代码生成器,这样可以在一定程度上较少代码密度,也可以将它理解为一种代码重用的手段,间接地减少不小心所造成的错误
单井号’#’:
“#”是一种预处理运算符,它的功能是将其后面的宏参数进行【字符串化】操作,简单说就是在对它所引用的宏变量通过替换后再起左右各加上一个双引号,用比较官方的话说就是讲语言符号转化为字符串,例如
1 | #define STR(X) #x |
- 关于宏定义中的do-while循环
PHP源码中大量使用了宏操作,比如PHP5.3新增的垃圾收集机制中的一段代码:
1 |
|
这段代码,在宏定义中使用了do{}while(0)语句模式,多行宏这种格式已经是一宗公认的编写方式了。
为什么这么写呢?
因为在do-while循环语句是先执行循环再判断条件是否成立,所以说至少会执行一次,当使用do-while(0)时,由于条件肯定是false,代码也肯定只执行一次
肯定执行一次的代码为什么要放在do-while语句里呢,这种方式适用于宏定义中存在多语句的情况,如下所示代码:
1 |
|
代码预处理后变成:
1 | if (expr) |
这样if-else的结构就被破坏了if后面有两个语句,这样是无法编译通过的,那为什么非要do-while而不是简单的用{}括起来呢。 这样也能保证if后面只有一个语句。例如上面的例子,在调用宏TEST的时候后面加了一个分号, 虽然这个分号可有可无, 但是出于习惯我们一般都会写上。 那如果是把宏里的代码用{}括起来,加上最后的那个分号。 还是不能通过编译。 所以一般的多表达式宏定义中都采用do-while(0)的方式。
了解了do-while循环在宏中的作用,再来看”空操作”的定义。由于PHP需要考虑到平台的移植性和不同的系统配置, 所以需要在某些时候把一些宏的操作定义为空操作。例如在sapi\thttpd\thttpd.c文件中的VEC_FREE():
1 |
这里涉及到条件编译,在定义了SERIALIZE_HEADERS宏的时候将VEC_FREE()定义为如上的内容,而没有定义时, 不需要做任何操作,所以后面的宏将VEC_FREE()定义为一个空操作,不做任何操作,通常这样来保证一致性, 或者充分利用系统提供的功能。
有时也会使用如下的方式来定义“空操作”,这里的空操作和上面的还是不一样,例如很常见的Debug日志打印宏:
1 |
在编译时如果定义了DEBUG则将LOG_MSG当做printf使用,而不需要调试,正式发布时则将LOG_MSG()宏定义为空, 由于宏是在预编译阶段进行处理的,所以上面的宏相当于从代码中删除了。
上面提到了两种将宏定义为空的定义方式,看上去一样,实际上只要明白了宏都只是简单的代码替换就知道该如何选择了。
- ‘#line’ 预处理
1 |
‘#line预处理’用于改变当前的行号(LINE)和文件名(FILE)。 如上所示代码,将当前的行号改变为838,文件名Zend/zend_language_scanner.c 它的作用体现在编译器的编写中,我们知道编译器对C 源码编译过程中会产生一些中间文件,通过这条指令, 可以保证文件名是固定的,不会被这些中间文件代替,有利于进行调试分析。
- PHP中的全局变量宏
在PHP代码中经常能看到一些类似PG(), EG()之类的函数,他们都是PHP中定义的宏,这系列宏主要的作用是解决线程安全所写的全局变量包裹宏, 如$PHP_SRC/main/php_globals.h文件中就包含了很多这类的宏。例如PG这个PHP的核心全局变量的宏。 如下所示代码为其定义。
1 |
|
PHP运行时的一些全局参数, 这个全局变量为如下的一个结构体,各字段的意义如字段后的注释:
1 | // 在PHP启动并读取php.ini文件时就会对这些字段进行赋值, 而用户空间的ini_get()及ini_set()函数操作的一些配置也是对这个全局变量进行操作的 |
v1.5.2