原文: http://blog.ircmaxell.com/2012/03/phps-source-code-for-php-developers.html
作为一个开发者,我发现在我的日常工作中越来越多地查看PHP的源码。在为了弄清楚奇怪的边界问题和为什么某些问题应该发生的却没有发生而去理解背后究竟发生了什么事情的时候非常有用。在文档缺失、不完整或者错误的情况下也很有用。因此,我已经决定通过一系列的文章来分享我学到的知识,给予PHP开发者们足够的知识去真正阅读PHP的C语言源码。你并不需要有C语言的基础(我们会总结一些基础),但如果有的话会更有帮助。
这是这个系列的第一篇文章。在这篇文章,我们会谈论PHP程序的基础:在哪里找到它,基本的代码结构和一些最基础的C语言概念。需要说明的是,这一系列文章的目标是获得源码的阅读理解能力。这意味着为了过一下某些点,某些概念会被简化而不是太复杂的描述。这不会给阅读造成明显的差异,但如果你想为源码做贡献,则还有更多的知识需要补充。在我做简化的时候,我会尽量指出这些简化。
另外,这系列文章是基于5.4版本的源码,在不同版本中,大部分概念都是一样的,但这里,我们需要针对这次的文章有一个版本的定义(为了让新的版本出来后接下来的文章更容易地遵循)。
那么,我们可以开始了吧?
下载PHP源码最简单的方式是通过 PHP的SVN仓库。对于这此文章,我们检出(check out)了5.4的分支。这对于成为PHP的前沿或者真正的开发PHP(解决bugs,实现特性等等)来说是非常棒的。值得注意的是,PHP社区正在(这篇文章正在写的时候)将源码迁移到GIT仓库中。一旦迁移完成,我会更新这篇文章以达到标准。(译者注:译者翻译的时候PHP已经迁移到GIT仓库了)。
事实上,下载源码对我们的目的来说并不是真正的有用。我们不想编辑它,我们只是想使用它和跟踪它是如何运行的。我们可以下载它,然后导入到一个好的IDE中,在这些IDE中我们可以点击跳到函数的定义和声明,当我发现这比想象中略困难。我有一个更好的解决方案。
事实证明,PHP社区在维护一个对于我们来说一个 非常好的工具。那就是 lxr.php.net。这主要是一个自动生成可搜索的源码列表,而且有语法高亮和函数全部有链接的。这个是我几乎只用来浏览C源码的工具,实在太棒(即使在我写补丁的时候,我依然到lxr而不是我正在开发的代码库)。我们还不会讲到如何做更有效的搜索,但我们会在谈论PHP核心函数的时候讲到。
从这里开始,我们将开始谈论PHP5.4。为了达到这目的,我们会使用 这个lxr链接作为其他文章的基础。当我提到“5.4的根目录”的时候,我就是说这个页面。
那么,既然我们可以查看源码目录了,那么我们来谈谈这里面都有什么吧。
那么,当你查看列在5.4的根目录的文件和目录时,还有很多可以研究。我希望你只关注两个目录: ext和 Zend。其他的文件和目录对于PHP扩展和开发来说很重要,但对于我们的目的来说,我们完全可以忽略它们。那么,为什么这两个目录那么重要呢?
PHP程序被分为,你猜对了,两个主要的部分。第一部分是Zend引擎,控制PHP代码运行时候的运行环境。它处理PHP提供的所有“语言层”的特性,包括:变量,表达式,语法解析,代码执行和错误处理。没有这个引擎,就没有PHP。引擎的源码放在了 Zend目录。
PHP第二个核心的部分,是包含在PHP里面的扩展。这些扩展包括我们可以在PHP调用的每一个核心函数(例如 strpos, substr, array_diff, mysql_connect等等)。也包括核心的类( MySQLi, SplFixedArray, PDO等等)。
在核心代码中,决定在哪里找到你想查看的功能最简单的方法是,查看PHP的 文档首页。PHP的文档也被分为两个主要的部分(为了达到我们的目的), 语言参考和 函数参考。作为一个庞大的概括,如果你想查看的是在语言参考中的定义,很有可能可以在 Zend文件夹找到。如果是在函数参考中,可以在 ext文件夹中找到。
这部分不是为了成为C的入门,而是一个“读者的配套指南”。有如下概念:
在C里面,变量是静态和强类型的。这意味着变量必须要使用一个类型定义之后才能使用。一旦定义之后,你不能改变它的类型(你可以在之后转换成其他类型,但你需要使用不同的变量来实现)。因为,在C语言里面,变量并不真实地存在。它们只是为了我们使用的方便的内存地址的标签。正因为如此,C语言没有PHP中的引用。取而代之,它有指针。为了我们的目的,把指针想象成指向其他变量的变量。把它当作PHP中变量的变量。
那么,通过上面的描述,我们来谈论一下变量的语法。C语言没有使用任何的前缀来标识变量。因此,要说出它们的不同的唯一方式(为了达到我们的目的)是查看它们的定义。如果你在函数的顶部(或者函数的声明)看到在类型和空格之后的字符,那就是变量。一个要说明的关键点是变量名前面可以有一个或这多个符号。星号(*)表明变量是指向某个类型的指针(一个引用)。两个星号表明变量是指向指针的指针。三个星号表明变量是指向一个指向其他指针的指针。
这个间接寻址非常重要,因为PHP内部使用很多的双层指针。这是因为引擎需要能够传递块数据(PHP变量),和所有有趣的类型如PHP引用,写时复制以及对象引用等等。因此,只要意识到**ptr意味着我们正使用两层的引用(不是变量的引用,而是一个数据引用的引用)。这又一点迷惑,但如果引用对你来说是完全新的知识,我建议你阅读一下这方面的知识(尽管我们的目的是不用必需阅读C)。会有帮助的。
现在,另一个理解指针的事情是它们是如何在C的数组里应用的(不是PHP的数组,而是C语言中的数组)。因为指针是内存地址,我们可以通过分配一块的内存来定义一个数组,然后通过递增指针来遍历它。正常情况下,我们可以使用代表一个字符(8位)的C的数据类型char来存储字符串中的一个字符。但我们也可以像使用数组那样使用它来访问字符串后面的字节。因此,我们可以只在第一个字节里存储一个指针而不是存储正一个字符串在变量中。然后,我们可以递增指针(增加它的内存地址)来遍历整个字符串。
char *foo = "test";// foo 是指向"t"在内存的片段保存"test"的指针// 要访问"e",我们可以通过下面的方式:char e = foo[1];char e = *(foo + 1);char e = *(++foo);
要另外阅读C语言重点的变量和指针,查看这本 很好的免费书籍。
C在编译之前使用一步叫做“预处理”的步骤。这一步包含优化和根据你传递给编译器的选项动态使用部分代码。我们将谈论两个主要的预处理器说明:条件语句和宏。
条件语句允许代码在编译输出或者不是基于定义时被引入。这看起来很像下面的例子。这允许不同的代码根据不同的操作系统被使用(因此尽管它们使用不同的API,也可以在Windows和Linux中很好的使用)。另外,它允许一部分代码被引入或者不是基于定义的指示。事实上,这是配置步骤中如何编译PHP的执行过程。
#define FOO 1#if FOOFoo is defined and not 0#elseFoo is not defined or is 0#endif#ifdef FOOFoo is defined#elseFoo is not defined#endif
另一个说明我叫它做宏。这是最简单简化代码的迷你函数。它们不是真正的函数,但是在编译预处理是会执行简单的文本替换。因此,宏不会真正地调用函数。你可以为函数定义写一个宏(事实上,PHP就是这么做的,但我们会在后面的文章中深入了解这个)。我想说的是,宏允许在预处理编译时使用更简单的代码。
#define FOO(a) ((a) + 1)int b = FOO(1); // Converted to int b = 1 + 1
最后这一部分,我们需要了解的是两种在C源码使用的类型的文件。主要有两种文件: .c和 .h。 .c文件是包含了源码准备编译的文件。通常来说, .c文件包含了不能分享到其他文件的私有函数的实现。 .h(或者说头文件)定义了在 .c文件中可以被其他文件看到的函数,包括预处理宏。头文件定义公共API的方式,是通过不使用函数体重新声明函数的签名(跟PHP中的接口和抽象方法相似)。这样,源码就可以通过头文件 链接在一起了。
这个系列的下一部分文章,我们即将讨论内部函数在C里面是怎么定义的。因此你可以跳到任意的内部函数(比如 strlen)查看它的定义和它是如何工作的。保持这个节奏。