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中所有数据类型。





  
原文地址:https://www.cnblogs.com/smilealgernon/p/3052676.html