文章来自:http://www.hoohack.me/2016/02/10/understanding-phps-internal-function-definitions-ch

原文:https://nikic.github.io/2012/03/16/Understanding-PHPs-internal-function-definitions.html

欢迎来到"给PHP开发者的PHP源码"系列的第二部分。

在上一篇中,ircmaxell说明了你可以在哪里找到PHP的源码,它的基本目录结构以及简单地介绍了一些C语言(因为PHP是用C语言来写的)。如果你错过了那篇文章,在你开始读这篇文章之前也许你应该读一下它。

在这篇文章中,我们谈论的是定位PHP内部函数的定义,以及理解它们的原理。

如何找到函数的定义

作为开始,让我们尝试找出strpos函数的定义。

尝试的第一步,就是去PHP 5.4根目录然后在页面顶部的搜索框输入strpos。搜索的结果是一个很大的列表,展示了strpos在PHP源码中出现的位置。

因为这个结果对我们并没有太大的帮助,我们使用一个小技巧:我们搜索"PHP_FUNCTION strpos"(不要漏了双引号,它们很重要),而不是strpos.

现在我们得到两个入口链接:

/PHP_5_4/ext/standard/
  php_string.h 48 PHP_FUNCTION(strpos);
  string.c 1789 PHP_FUNCTION(strpos)

第一个要注意的事情是,两个位置都是在ext/standard文件夹。这就是我们希望找到的,因为strpos函数(跟大部分string,array和文件函数一样)是standard扩展的一部分。

现在,在新标签页打开两个链接,然后看看它们背后藏了什么代码。

你会看到第一个链接带你到了php_string.h文件,它包含了下面的代码:

  

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

这就是一个典型的头文件(以.h后缀结尾的文件)的样子:单纯的函数列表,函数在其他地方定义。事实上,我们对这些并不感兴趣,因为我们已经知道我们要找的是什么。

第二个链接更有趣:它带我们到string.c文件,这个文件包含了函数真正的源代码。

在我带你一步一步地查阅这个函数之前,我推荐你自己尝试理解这个函数。这是一个很简单的函数,尽管你不知道真正的细节,但大多数代码看起来都很清晰。

PHP函数的骨架

所有的PHP函数都使用同一个基本结构。在函数顶部定义了各个变量,然后调用zend_parse_parameters函数,然后到了主要的逻辑,当中有RETURN_***php_error_docref的调用。

那么,让我们以函数的定义来开始:

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


 

第一行定义了一个指向zval的指针needle。zval是在PHP内部代表任意一个PHP变量的定义。它真正是怎么样的会在下一篇文章重点谈论。

第二行定义了指向单个字符的指针haystack。这时候,你需要记住,在C语言里面,数组代表指向它们第一个元素的指针。比如说,haystack变量会指向你所传递的$haystack字符串变量的第一个字符。haystack + 1会指向第二个字符,haystack + 2指向第三个,以此类推。因此,通过逐个递增指针,可以读取整个字符串。

那么问题来了,PHP需要知道字符串在哪里结束。不然的话,它会一直递增指针而不会停止。为了解决这个问题,PHP也保存了明确的长度,这就是haystack_len变量。

现在,在上面的定义中,我们感兴趣的是offset变量,这个变量用来保存函数的第三个参数:开始搜索的偏移量。它使用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代码库的很多地方。是线程安全资源管理器(TSRM)的一部分,它保证PHP不会在多线程之间混乱变量。这对我们来说不是很重要,当你在代码中看到TSRMLS_CC(或者TSRMLS_DC)的时候,忽略它就行。(有一个奇怪的地方你需要注意的是,在"argument"之前没有逗号。这是因为不管你是否使用线程安全创建函数,该宏会被解释为空或者, trsm_ls。因此,逗号是宏的一部分。)

现在,我们来到重要的东西:"sz\|l"字符串标记了函数接收的参数。:

s  // 第一个参数是字符串
z  // 第二个参数是一个zval结构体,任意的变量
|  // 标识接下来的参数是可选的
l  // 第三个参数是long类型(整型)

除了s,z,l之外,还有更多的标识类型,但是大部分都能从字符中清楚其意思。例如b是boolean,d是double(浮点型数字),a是array,f是回调(function),o是object。

接下来的参数&haystack&haystack_len&needle&offset指定了需要赋值的参数的变量。你可以看到,它们都是使用引用(&)传递的,意味着它们传递的不是变量本身,而是指向它们的指针。

这个函数调用之后,haystack会包含haystack字符串,haystack_len是字符串的长度,needle是needle的值,offset是开始的偏移量。

而且,这个函数使用FAILURE(当你尝试传递无效参数到函数时会发生,比如传递一个数组赋值到字符串)来检查。这种情况下zend_parse_parameters函数会抛出警告,而此函数马上返回(会返回null给PHP的用户层代码)。

在参数解析完毕以后,主函数体开始:

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


这段代码做的事情很明显,如果offset超出了边界,一个E_WARNING级别的错误会通过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 is filled with Z_STRLEN_PP(tmp)
// %s is filled with 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行非常清晰:这个分支只会在needle为字符串的情况下执行,而且如果它是空的话会抛出错误。然后到了比较有趣的一部分:php_memnstr被调用了,这个函数做了主要的工作。跟往常一样,你可以点击该函数名然后查看它的源码。

php_memnstr返回指向needle在haystack第一次出现的位置的指针(这就是为什么found变量要定义为char *,例如,指向字符的指针)。从这里可以知道,偏移量(offset)可以通过减法被简单地计算,可以在函数的最后看到:

RETURN_LONG(found - haystack);

最后,让我们来看看当needle作为非字符串的时候的分支:

else {
    if (php_needle_char(needle, needle_char TSRMLS_CC) != SUCCESS) {
        RETURN_FALSE;
    }
    needle_char[] = ;

    found = php_memnstr(haystack + offset,
                        needle_char,
                        ,
                        haystack + haystack_len);
}


我只引用在手册上写的"如果 needle 不是一个字符串,那么它将被转换为整型并被视为字符顺序值。"这基本上说明,除了写strpos($str, 'A'),你还可以写strpos($str, 65),因为A字符的编码是65。

如果你再查看变量定义,你可以看到needle_char被定义为char needle_char[2],即有两个字符的字符串,php_needle_char会将真正的字符(在这里是'A')到needle_char[0]。然后strpos函数会设置needle_char[1]为0。这背后的原因是因为,在C里面,字符串是使用'\0'结尾,就是说,最后一个字符被设置为NUL(编码为0的字符)。在PHP的语法环境里,这样的情况不存在,因为PHP存储了所有字符串的长度(因此它不需要0来帮助找到字符串的结尾),但是为了保证与C函数的兼容性,还是在PHP的内部实现了。

Zend functions

我对strpos这个函数感觉好累,让我们找另一个函数吧:strlen。我们使用之前的方法:

从PHP5.4源码根目录开始搜索strlen。

你会看到一堆无关的函数的使用,因此,搜索“PHP_FUNCTION strlen”。当你这么搜索的时候,你会发现一些奇怪的事情发生了:没有任何的结果。

原因是,strlen是少数通过Zend Engine而不是PHP扩展定义的函数。这种情况下,函数不是使用PHP_FUNCTION(strlen)定义,而是ZEND_FUNCTION(strlen)。因此,我们也要搜索“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);
}

这个函数实现太简单了,我不觉得我还需要进一步的解释。

方法

我们会谈论类和对象如何工作的更多细节在其他文章里,但作为一个小小的剧透:你可以通过在搜索框搜索ClassName::methodName来搜索对象方法。例如,尝试搜索SplFixedArray::getSize

下一部分

下一部分会再次发表在。会谈论到zval是什么,它们是怎么工作的,以及它们是怎么在源码中被使用的(所有的Z_*宏)。

[译] 理解PHP内部函数的定义(给PHP开发者的PHP源码-第二部分)的更多相关文章

  1. [译] 理解数组在 PHP 内部的实现(给PHP开发者的PHP源码-第四部分)

    文章来自:http://www.hoohack.me/2016/02/15/understanding-phps-internal-array-implementation-ch 原文:https:/ ...

  2. 【译】PHP的变量实现(给PHP开发者的PHP源码-第三部分)

    文章来自:http://www.aintnot.com/2016/02/12/phps-source-code-for-php-developers-part3-variables-ch 原文:htt ...

  3. 简单理解 OAuth 2.0 及资料收集,IdentityServer4 部分源码解析

    简单理解 OAuth 2.0 及资料收集,IdentityServer4 部分源码解析 虽然经常用 OAuth 2.0,但是原理却不曾了解,印象里觉得很简单,请求跳来跳去,今天看完相关介绍,就来捋一捋 ...

  4. [译] 给PHP开发者的PHP源码-第一部分-源码结构

    文章来自:http://www.hoohack.me/2016/02/04/phps-source-code-for-php-developers-ch 原文:http://blog.ircmaxel ...

  5. WmS详解(二)之如何理解Window和窗口的关系?基于Android7.0源码

    上篇博客(WmS详解(一)之token到底是什么?基于Android7.0源码)中我们简要介绍了token的作用,这里涉及到的概念非常多,其中出现频率最高的要数Window和窗口这一对搭档了,那么我们 ...

  6. vue系列---理解Vue中的computed,watch,methods的区别及源码实现(六)

    _ 阅读目录 一. 理解Vue中的computed用法 二:computed 和 methods的区别? 三:Vue中的watch的用法 四:computed的基本原理及源码实现 回到顶部 一. 理解 ...

  7. 书籍《深入理解Spring Cloud 与微服务构建》勘误、源码下载

    转载请标明出处: https://blog.csdn.net/forezp/article/details/79638403 本文出自方志朋的博客 文章勘误 错误在所难免,欢迎大家批评指正,在文章下方 ...

  8. Spring基于XML方式加载Bean定义信息(又名:Spring IOC源码时序图)-图解

  9. 深入理解 path-to-regexp.js 及源码分析

    阅读目录 一:path-to-regexp.js 源码分析如下: 二:pathToRegexp 的方法使用 回到顶部 一:path-to-regexp.js 源码分析如下: 我们在vue-router ...

随机推荐

  1. 《learning hard C#学习笔记》读书笔记(20)异步编程

      20.1 什么是异步编程异步编程就是把耗时的操作放进一个单独的线程中进行处理. 20.2 同步方式存在的问题   namespace 异步编程 { public partial class For ...

  2. JavaScript浮动广告代码,容纯DIV/CSS对联漂浮广告代码,兼容性非常好的js右下角与漂浮广告代码

    基于JavaScript代码实现随机漂浮图片广告,javascript图片广告 在网上有很多这样的代码,不过未必符合W3C标准,因为在头部加上<!DOCTYPE html>类似标签之后,漂 ...

  3. Chrome 桌面通知Notification

    今天2016-11-18号,最新chrome版本:V50 在网上找了好久都用不了,因为chrome版本已经是V50了,chrome在V22版本之后就取消掉window.webkitNotificati ...

  4. C++ 系列:内存布局

    转载自http://www.cnblogs.com/skynet/archive/2011/03/07/1975479.html 为什么需要知道C/C++的内存布局和在哪可以可以找到想要的数据?知道内 ...

  5. Python debug

  6. -webkit-tap-highlight-color

    css3中有henduo 新属性,tap-highlight-color;是用于元素在移动设备IOS和adnroid上被触发点击事件时,响应的背景框的颜色. 例如在Adnroid版本的微信中,点击a标 ...

  7. 2016 ACM/ICPC Asia Regional Dalian Online 1008 Function 二分+RMQ

    Time Limit: 7000/3500 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others)Total Submissio ...

  8. iOS SQLite3的使用

    1.创建可修改的数据库文件 //应用包内的内容是不可写的,所以需要把应用包内的数据库拷贝一个副本到资源路径去 - (void)createEditableDatabase{ BOOL success; ...

  9. 关于WORD文档的读取乱码问题

    一直以来都是用File类操作txt文档,今天想尝试能不能打开word文档,无奈,尝试了UTF8,Unicode,Default....等编码方式,打开文件都是乱码,电脑甚至发出警报声. 以下只取一种编 ...

  10. bzoj1026数位dp

    基础的数位dp 但是ce了一发,(abs难道不是cmath里的吗?改成bits/stdc++.h就过了) #include <bits/stdc++.h> using namespace ...