标签归档:深入理解PHP内核

TIPI0103–PHP实现中的常用代码

第三节 PHP实现中的常用代码

在PHP的源码中经常会看到一些宏或一些对于刚开始看源码的童鞋比较纠结的代码。这里提取中间的一些进行说明。

1. 关于##和#


在PHP的宏定义中,最常见的要数双井号。

双井号

在C语言的宏中,”##”被称为 连接符(concatenator),用来把两个语言符号(Token)组合成单个语言符号。这里的语言符号不一定是宏的变量。并且双井号不能作为第一个或最后一个元素存在。如下所示源码:

#define PHP_FUNCTION            ZEND_FUNCTION
#define ZEND_FUNCTION(name)             ZEND_NAMED_FUNCTION(ZEND_FN(name))
#define ZEND_FN(name) zif_##name
#define ZEND_NAMED_FUNCTION(name)       void name(INTERNAL_FUNCTION_PARAMETERS)
#define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, \
zval *this_ptr, int return_value_used TSRMLS_DC

PHP_FUNCTION(count);

//  预处理器处理以后, PHP_FUCNTION(count);就展开为如下代码
void zif_count(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC)

宏ZEND_FN(name)中有一个”##”,它的作用一如之前所说,是一个连接符,将zif和宏的变量name得值连接起来。

单井号

“#”的功能是将其后面的宏参数进行 字符串化操作 ,简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号,用比较官方的话说就是将语言符号(Token)转化为字符串。

2. 关于宏定义中的do-while


如下所示为PHP5.3新增加的垃圾收集机制中的一段代码:

#define ALLOC_ZVAL(z)                                   \
do {                                                \
    (z) = (zval*)emalloc(sizeof(zval_gc_info));     \
    GC_ZVAL_INIT(z);                                \
} while (0)

如上所示的代码,在宏定义中使用了 do{ }while(0) 语句格式。如果我们搜索整个PHP的源码目录,会发现这样的语句还有很多。那为什么在宏定义时需要使用do-while语句呢? 我们知道do-while循环语句是先执行再判断条件是否成立, 所以说至少会执行一次。当使用do{ }while(0)时代码肯定只执行一次, 肯定只执行一次的代码为什么要放在do-while语句里呢? 这种方式适用于宏定义中存在多语句的情况。如下所示代码:

#define TEST(a, b)  a++;b++;

if (expr)
    TEST(a, b);
else
    do_else();

代码进行预处理后,会变成:

if (expr)
    a++;b++;
else
    do_else();

这样if-else的结构就被破坏了: if后面有两个语句, 这样是无法编译通过的, 那为什么非要do-while而不是简单的用{}括起来呢.这样也能保证if后面只有一个语句。 例如上面的例子,在调用宏TEST的时候后面加了一个分号, 虽然这个分号可有可无, 但是出于习惯我们一般都会写上. 那如果是把宏里的代码用{}括起来,加上最后的那个分号. 还是不能通过编译. 所以一般的多表达式宏定义中都采用do-while(0)的方式.

3. #line 预处理


#line 838 "Zend/zend_language_scanner.c"

#line预处理用于改变当前的行号和文件名。
如上所示代码,将当前的行号改变为838,文件名Zend/zend_language_scanner.c
它的作用体现在编译器的编写中,我们知道编译器对C 源码编译过程中会产生一些中间文件,通过这条指令,可以保证文件名是固定的,不会被这些中间文件代替,有利于进行调试分析。

作者:TIPI Team

TIPI0101-环境搭建

第一节 环境搭建

在开始学习PHP实现之前, 我们首先需要一个实验和学习的环境. 下面介绍一下怎样在*nix环境下准备和搭建PHP环境. (*nix指的是类Unix环境,比如各种Linux发行版,FreeBSD, OpenSolaris, Mac OS X等操作系统)

1.获取PHP源码

为了学习PHP的实现,首先我们要下载源代码.下载源码首选是去PHP官方网站 http://php.net/downloads.php下载, 如果你喜欢是用类似svn/git这些版本控制软件,喜欢svn的读者可以去http://www.php.net/svn.php上 签出源代码,或者如果你喜欢用git, 则可以去http://github.com/php/php-src上clone一个. 个人比较喜欢用版本控制软件签出代码, 这样的好处是能看到php每次修改的内容及日志信息, 如果自己修改了其中的某些内容也能快速的查看到.

2.准备编译环境

在*nix环境下,首先需要gcc编译构建环境. 如果你是用的是Ubuntu或者是用apt做为包管理的系统,可以通过如下命令快速安装.

sudo apt-get install build-essential

如果用的是Mac OS的话,则需要安装Xcode。Xcode可以在Mac OS X的安装盘中找到,如果你有Apple ID的话,也可以登陆苹果开发者网站http://developer.apple.com/下载。

3. 编译

下一步就可以开始编译了,本文只简单介绍基本的编译过程,不包含apache的php支持以及mysql等模块的编译。相关资料请百度或google之。 假设源代码下载到了~/php-src的目录中,执行buildconf命令以生成所需要的Makefile文件

cd ~/php-src
./buildconf

执行完以后就可以开始configure了, configure有很多的参数, 比如指定安装目录, 是否开启相关模块等选项

./configure --help # 查看可用参数

为了尽快得到可以测试的环境,我们就不加其他参数了.直接执行./configure就可以了. 以后如果需要其他功能可以重新编译. 如果configure命令出现错误,可能是缺少php所依赖的库,各个系统的环境可能不一样. 出现错误可根据出错信息上网搜索. 直到完成configure. configure完成后我们就可以开始编译了.

make

在*nix下编译过程序的读者应该都熟悉经典的configure make, make install吧. 执行make之后是否需要make install就取决于你了. 如果install的话 最好在configure的时候是用prefix参数指定安装目录, 不建议安装到系统目录, 避免和系统原有的php冲突.

在make 完以后,~/php-src目录里就已经有了php的可以执行文件. 执行一下命令:

cd ~/php-src
./sapi/cli/php -v

如果看到输出php版本信息则说明咱成功了. 如果是make install的话则执行 $prefix/bin/php 这个路径的php, 当然如果是安装在系统目录或者你的prefix 目录在$PATH环境变量里的话,直接执行php就行了.

后续的学习中可能会需要重复configure make 或者 make && make install 这几个步骤。

作者:TIPI Team