PHP开发者的 PHP源代码--之二内部函数定义

6 篇文章 0 订阅
4 篇文章 0 订阅

欢迎来到“PHP开发者的PHP源代码”系列的第二部分。
在前面的部分中,ircmaxell 博客 解释了在哪里可以找到PHP源代码及其基本结构,并对C进行了一点介绍(因为这是编写PHP的语言)。如果你错过了那篇文章,你应该在开始本篇之前先读一下。

在本文中,我们将讨论PHP代码库中的内部函数的定义,以及理解它们的含义。

如何找到函数定义

首先,让我们试着找到strpos函数的定义。
首先要做的是,在PHP5.4 Source code root 主页面,在页面顶部的搜索框中输入strpos。结果列出是在PHP源代码中出现strpos的很大列表。
由于这对我们没有什么帮助,我们使用了一个小技巧:我们搜索的是“PHP FUNCTION strpos”(不要忘记引号,它们很重要)。
现在我们只剩下两个条目了:
/PHP_5_4/ext/standard/
php_string.h 48 PHP_FUNCTION(strpos);
string.c 1789 PHP_FUNCTION(strpos)
首先要注意的是,这两个事件都在ext/standard文件夹中。这正是人们期望找到它们的地方,因为strpos函数(与其他所有字符串、数组和文件函数一起)是标准扩展的一部分。
现在,在新的标签页中打开两个链接,看看它们背后隐藏着什么代码。
你会发现第一个链接会引导你到php_string.h文件,它的代码是这样的:

// ...
PHP_FUNCTION(strpos);
PHP_FUNCTION(stripos);
PHP_FUNCTION(strrpos);
PHP_FUNCTION(strripos);
PHP_FUNCTION(strrchr);
PHP_FUNCTION(substr);
// ...

这就是典型的头文件(一个以.h结尾的文件):一个普通的函数列表,这些函数在其他.c 文件被定义。我们对这个并不感兴趣,因为我们已经知道我们在寻找什么。
第二个链接更有趣:它会引导我们到 string.c 文件,它是包含函数定义的实际源代码的c文件。
在我一步步地介绍代码之前,我建议您自己尝试理解这个函数。这是一个非常简单的函数,即使你不知道确切的细节,大多数事情你应该是清楚的。

PHP函数的骨架

所有PHP函数都具有相同的基本结构。在顶部有几个变量声明,然后是一个zend_parse_parameters调用,然后是主逻辑,返回语句和php_error_docref混合调用。
那么,让我们从变量声明开始吧:

zval *needle;
char *haystack;
char *found = NULL;
char  needle_char[2];
int   haystack_len;
long  offset = 0;

第一行声明为指向zval的指针。zval可以表示是任何PHP值。
第二行声明了作为一个字符串的指针。此时,您必须记住,在C中,指针指向数组的第一个值。也就是说,指针haystack将指向你传入的字符串的第一个字符。然后,haystack +1将指向第二个字符,haystack + 2到第三个,以此类推。因此,我们可以读取整个字符串通过增加指针 + 1。这里出现的问题是PHP必须知道字符串何时结束。否则,它就会不断地增加指针,而不会停止。为了解决这个问题,PHP还存储了一个字符串的长度,这里的变量是 haystack_len。
我们感兴趣的最后一个声明 offset是偏移量变量,它将用于存储函数的第三个参数:开始搜索的偏移量。它被声明为long类型,它是一个整型数据类型,就像int一样。这long, int两者之间的区别在这里并不重要,但是您应该知道PHP整数存储在long 类型中,而字符串长度存储在int 类型中。
现在让我们来看看下面的三行:

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|l", &haystack, &haystack_len, &needle, &offset) == FAILURE) {
    return;
}

这些行基本上做的是,通过参数将值传递给函数, 并执行此函数。
第一个参数是多少个参数传递到函数内,参数是由ZEND_NUM_ARGS()定义的。
第二个参数是TSRMLS_CC, PHP 独有的值, 在 PHP 源代码中会经常看到它,它是 线程安全资源管理的一部分,来保证PHP在不同的多线程中不能混淆变量。这对我们来说不重要,下一次在看到它,将它忽律掉。(你可能注意到的一个奇怪的地方是,在这个“TSRMLS_CC”之前没有逗号。这取决于您是否使用了线程安全的机制,如果有逗号,因为逗号是宏的一部分,所以有逗号可能导致对其进行计算)

现在重要的是:“sz | l”字符串指定函数接受哪些参数:
S //第一个参数是string
z//第二个参数是val(任意值)
| //以后的参数(这里只有一个)是可选的,注意这个是 “竖线”
l //第三个参数是long(整数),注意是 字母 “l”
还有比s、z和l更多的类型说明符,但大多数都应该不能传递到这里。例如,b是一个布尔值,d是一个双(浮点数),a是一个数组,f是一个回调函数(函数),o是一个对象。

其余的参数都是传递地址,来源字符串地址、字符串长度地址、查询字符串地址和来源字符串偏移量地址。正如您所看到的,它们都是通过引用(&)传递的,这意味着不是通过传递变量本身,而是传递地址。
在这个调用之后,haystack将包含来源字符串,haystack_len包括来源字符串的长度,needle 是查找字符串,offset 是从来源字符串开始查询位置偏移量。
另外,该函数被检查为失败(如果您试图将无效的参数传递给函数,则会发生这种情况。例如将一个数组传递到本应该是 字符串参数)。在本例中,zend_parse_parameters将抛出一个警告,函数的代码将返回(最终返回null到用户区 PHP代码)。
在所有的参数传入后,函数开始如下:

if (offset < 0 || offset > haystack_len) {
    php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset not contained in string");
    RETURN_FALSE;
}

这段代码的作用非常明显。如果偏移量超出范围,则通过php_error_docref抛出一个警告级别错误,然后使用RETURN_FALSE宏返回false。
php_error_docref是在扩展中找到的错误函数(即ext文件夹)。这个名称来自于它对错误消息中的文档的引用。此外,还有zend_error函数,它主要用于Zend Engine,但也会不时地出现在扩展代码中。

这两个函数都使用类似于sprintf的格式,因此错误消息包含占位符,然后使用下面的参数填充这些占位符。这是一个例子:
php_error_docref(NULL TSRMLS_CC, E_WARNING, “Failed to write %d bytes to %s”, Z_STRLEN_PP(tmp), filename);
// %d 要被 Z_STRLEN_PP(tmp) 填充
// %s 要被filename 填充
看看下面的代码

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);
}

前5行应该是清楚的:只有当指针是字符串时才执行这个分支,如果是空的,则抛出一个错误。接下来是有趣的部分:php_memnstr被调用,它是执行主要工作的函数。与往常一样,您可以单击函数名来查看它的源代码。
php_memnstr返回当找到查询字符串第一次出现指针(这就是为什么变量被声明为char指针)。由此,可以很容易地通过两个指针相减来计算偏移量,正如在函数的结尾所看到的那样:
RETURN_LONG(found - haystack);

最后,让我们看一下当传入参数不是字符串时所采取的分支:

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);
}

我将从手册中引用这句话:“如果查询字符串不是字符串,它将被转换为一个整数,并转换为整数对应的ASCII字符应用。这基本上意味着,你不需要写strpos( str,A)strpos( str,65),因为A的ASC是65。
如果您查找变量声明,您将看到needle_char被声明为needle_char[2],即一个带有两个字符的数组。php_needle_char是实际字符(在我们例子中存放A)放入数组[0]中。然后,数组[1]赋值为0。这背后的原因是,在C语言中,字符串作为以0(asc码)结束,即最后一个字符设置为空字符(asc码 0)。在PHP环境中这没有多大意义,因为PHP显式存储的字符串长度(所以不需要0作为字符串结束),但这仍然是为了确保兼容的C函数并在PHP内部使用。

Zend 函数

我已经知道找strpos 函数的原理,所以我们试着找到另一个函数:strlen。我们用我们常用的方法来查找:
从PHP 5.4源代码根开始,尝试搜索strlen。
您将看到许多与此函数无关的用法,因此搜索“php_FUNCTION strlen”。在这样做的时候,你会注意到一些奇怪的事情:没有任何结果。
原因是strlen是少数函数之一,它不是由扩展定义的,而是由Zend Engine本身定义的。在这种情况下,函数不是被定义为php_FUNCTION(strlen),而是定义为zend_FUNCTION(strlen)。因此,我们必须搜索“ZEND_FUNCTION strlen”。

正如我们已经知道的,我们点击没有分号的链接,最后到达源代码。这将使我们在zend/zend_builtin_functions.c构建函数中得到以下定义:

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);
}

我不需要对这些代码做解释,因为函数比较简单。

方法

我们将讨论类和对象如何工作,要发表在不同的博客论坛之中,但是作为前面的一个小的窥视:您可以通过键入ClassName::methodName名称来搜索类方法。作为一个例子,尝试搜索SplFixedArray::getSize。

接下来的部分

下一部分将继续在ircmaxell的博客上发表。它将涵盖zvals是什么,它们如何工作以及如何在源代码中使用(所有这些Z_***宏…)
如果你喜欢这篇文章,你可以浏览我的其他文章,或者在Twitter上关注我。

本文章翻译自 Nikita Popov, Nikic 博客 PHP 源代码文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值