PHP源码阅读-Part2-函数
注:这篇文章的内容出自nikic的博客,这里只是翻译整理一下!
上一部分我们讨论了从哪里获取源码,以及源码的主要结构。这部分我们主要讨论,如何找到一个PHP核心函数的C源码,以及它的实现。
如何获得PHP核心函数的C源码
这里我们以strpos函数为例,首先进入PHP5.4源码的根目录,在搜索框中输入strpos,你会发现搜索出了很多,找起来很不方便,我们使用一点小小的技巧,在搜索在搜索框中输入"PHP_FUNCTION strpos"(注意:要加引号),现在,得到了两条搜索结果:
/PHP_5_4/ext/standard/ php_string.h 48 PHP_FUNCTION(strpos); string.c 1789 PHP_FUNCTION(strpos);
显然,php_string.h文件中是函数的声明。string.c文件中是函数的实现。数组、字符串等核心函数都是在ext目录下。
PHP函数
打开.c文件,看一下strpos函数的实现代码,源码里使用了很多C语言的宏,看起来可能有点犯怵。对比一下其他函数的实现方式,你会发现每个函数的实现基本上都有一些相同的结构。首先是声明各种变量,接着调用zend_parse_parameters,接下来是主体实现部分,过程中如果出现问题就RETURN_**,然后调用php_error_docref。
OK,首先我们看一下变量声明部分:
zval *needle; char *haystack; char *found = NULL; char needle_char[2]; long offset = 0; int haystack_len;
needle是指向zval结构体的一个指针,这是strpos函数的第二个参数,可以是任何类型,PHP的所有数据类型,映射到C语言中,都是用zval结构体实现的,这个我们下节中会讲到。
haystack是一个字符指针,它将指向传进来的字符串的第一个字符,haystack+1将指向第二个,haystack+2指向第三个,以此类推。所以,通过指针的递增,可以读到整个字符串。
这里有个问题,就是,我们必须要知道字符串的长度,否则指针一直会递增下去。为了解决这个问题,PHP定义了一个变量haystack_len,来存储字符串的长度。
我们可能感兴趣的最后一个变量是offset,这是PHP strpos函数的第三个参数,用来记录搜索的起至位置。在C源码中它被声明为long类型。而在PHP strpos中它是整型。这里只要记住PHP的整型其实对应的是C语言的long类型就行了,详细的内容下节会介绍到。接下来:
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|l", &haystack, &haystack_len, &needle, &offset) == FAILURE) { return; }
zend_parse_parameters实现的功能主要是把上面声明的变量,经过处理,然后返回(变量的引用)。第一个参数是参数的个数,通过宏 ZEND_NUM_ARGS()得到,下一个参数是TSRMLS_CC宏,你会发现这个奇怪的宏遍布PHP源码,它主要涉及到TSRM(Thread Safe Resource Mananger),这里我们不做深入的研究,以上两个参数没有用逗号分隔,是因为逗号已经包含在宏里。接下来的参数是"sz|l",这串字符表示:
-
s // 第一个参数是字符串(string)类型
z // 第二个参数是zval类型(混合)
| // 表示接下来的参数是可选的(这里只有一个)
l // 第三个参数是long类型
以上所描述的三个参数对应PHP核心函数strpos的三个参数,分别被定义为s、z、l类型,对应&haystack、&needle、&offset。另外b表示boolean类型,d表示浮点型(double),a表示数组(array)型,o表示对象(object),f表示回调函数(function)。
经过zend_parse_parameters的处理,haystack变量将存着被查找的字符串,needle存着要查找的字符串,offset存着偏移值,haystak_len存着haystack字符串的长度。
如果返回的是
FAILURE(比如:调用strpos传入不合法的参数),将会直接return;对应到PHP中,将会返回一个NULL值。如果参数通过了验证,接下来将是函数功能实现的主要部分:
if (offset < 0 || offset > haystack_len) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset not contained in string"); RETURN_FALSE; }
判断offset是否超出了haystack的范围,如果超出了,就使用php_error_docref函数,返回false。这里的
php_error_docref只是产生一个PHP手册的链接,对应着相应的错误,除非你在PHP配置文件中打开了这项功能,否则不会有什么效果。真正发出警告的是zend_error函数,这个函数在PHP引擎里很常见,当然扩展里也会用到。
if (Z_TYPE_P(needle) == IS_STRING) { if (!Z_STRLEN_P(needle)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Empty delimiter"); RETURN_FALSE; } found = php_memnstr(haystack + offset, Z_STRVAL_P(needle), Z_STRLEN_P(needle), haystack + haystack_len); }
这里判断needle是否为字符串,如果needle是字符串,但是为空,将会抛出错误,返回false。否则调用php_memnstr函数,你可以点击这个函数,查看它的具体实现。php_menstr返回了needle在haystack第一次出现的位置,是一个字符指针,赋给found。
我们接着再看if的另一个分支:
else { if (php_needle_char(needle, needle_char TSRMLS_CC) != SUCCESS) { RETURN_FALSE; } needle_char[1] = 0; found = php_memnstr(haystack + offset, needle_char, 1, haystack + haystack_len); }
如果needle不为字符串类型时,根据说明手册里,如果needle不为字符串,将会被转为整型,然后把这个整型对应为字符串来查找,也就是说strpos($str,'A')和strpos($str,65)是同样的效果。php_needle_char()就是用来实现这个功能,把转好的字符存放在needle_char[0]中,然后将needle_char[1]置0,这是c语言表示字符串的一种方法。接下来,和上面的一样。
ZEND函数
并不是所有的函数都定义在ext目录下,作为扩展,有很少一部分函数是由zend引擎实现的,比如strlen函数。你搜"PHP_FUNCTION strlen"是不会有结果的,这个函数的实现在Zend的目录下,所以你得搜"ZEND_FUNCTION strlen"。
ZEND_FUNCTION(strlen) { char *s1; int s1_len; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &s1, &s1_len) == FAILURE) { return; } RETVAL_LONG(s1_len); }
这个函数的实现很简单,这里就不再赘述了。
关于下一节
下一节我们将会讨论源码中zval数据结构,这个结构体对应这PHP中所有数据类型。