찾다
백엔드 개발PHP 튜토리얼PHP 소스 코드 - 내부 함수 소스 코드 분석
PHP 소스 코드 - 내부 함수 소스 코드 분석Jun 27, 2019 pm 06:06 PM
내파 기능PHP 소스 코드

PHP 소스 코드 - 내부 함수 소스 코드 분석

PHP에서 implode

  • PHP에서 implode의 기능은 1차원 배열의 값을 문자열로 변환하는 것입니다. 1차원 배열을 기억하세요. 다차원 배열이면 어떻게 될까요? 이번 분석에서는 이에 대해 논의하겠습니다.
  • 사실 공식 문서에서 우리는 implode에 두 가지 용도가 있다는 것을 알 수 있습니다. 이는 함수 서명에서 볼 수 있습니다:
// 方法1
implode ( string $glue , array $pieces ) : string
// 方法2
implode ( array $pieces ) : string
  • glue가 전달되지 않으면 내부 구현은 기본적으로 빈 문자열로 설정됩니다. .
  • 간단한 예를 통해 볼 수 있습니다:
$pieces = [
    123,
    ',是一个',
    'number!',
];
$str1 = implode($pieces);
$str2 = implode('', $pieces);

var_dump($str1, $str2);
/*
string(20) "123,是一个number!"
string(20) "123,是一个number!"
*/

implode 소스 코드 구현

  • PHP_FUNCTION(implode) 키워드로 검색하면 찾을 수 있습니다. 함수는 에 정의되어 있습니다. >extstandardstring.c <a href="https://github.com/php/php-src/blob/9ebd7f36b1bcbb2b425ab8e903846f3339d6d566/ext/standard/string.c#L1288" rel="nofollow noreferrer">라인 1288 a><code>PHP_FUNCTION(implode) 可以找到,该函数定义于 extstandardstring.c 文件中的 1288 行
  • 一开始的几行是参数声明相关的信息。其中 *arg2 是用于接收 pieces 参数的指针。
  • 在下方对 arg2 的判断中,如果 arg2 为空,则表示没有传 pieces 对应的值
if (arg2 == NULL) {
    if (Z_TYPE_P(arg1) != IS_ARRAY) {
        php_error_docref(NULL, E_WARNING, "Argument must be an array");
        return;
    }

    glue = ZSTR_EMPTY_ALLOC();
    tmp_glue = NULL;
    pieces = arg1;
} else {
    if (Z_TYPE_P(arg1) == IS_ARRAY) {
        glue = zval_get_tmp_string(arg2, &tmp_glue);
        pieces = arg1;
    } else if (Z_TYPE_P(arg2) == IS_ARRAY) {
        glue = zval_get_tmp_string(arg1, &tmp_glue);
        pieces = arg2;
    } else {
        php_error_docref(NULL, E_WARNING, "Invalid arguments passed");
        return;
    }
}

不传递 pieces 参数

  • 在不传递 pieces 参数的判断中,即 arg2 == NULL,主要是对参数的一些处理
  • 将 glue 初始化为空字符串,并将传进来的唯一的参数,赋值给 pieces 变量,接着就调用 php_implode(glue, pieces, return_value);

十分关键的 php_implode

  • 无论有没有传递 pieces 参数,在处理好参数后,最终都会调用 PHPAPI 的相关函数 php_implode,可见,关键逻辑都是在这个函数中实现的,那么我们深入其中看一看它
  • 在调用 php_implode 时,出现了一个看起来没有被声明的变量 return_value。没错,它似乎就是凭空出现的
  • 通过谷歌搜索 PHP源码中 return_value,找到了答案
  • 原来,这个变量是伴随着宏 PHP_FUNCTION 而出现的,而此处 implode 的实现就是通过 PHP_FUNCTION(implode) 来声明的。而 PHP_FUNCTION 的定义是:
#define PHP_FUNCTION            ZEND_FUNCTION
// 对应的 ZEND_FUNCTION 定义如下
#define ZEND_FUNCTION(name)                ZEND_NAMED_FUNCTION(ZEND_FN(name))
// 对应的 ZEND_NAMED_FUNCTION 定义如下
#define ZEND_NAMED_FUNCTION(name)        void ZEND_FASTCALL name(INTERNAL_FUNCTION_PARAMETERS)
// 对应的 ZEND_FN 定义如下
#define ZEND_FN(name) zif_##name
// 对应的 ZEND_FASTCALL 定义如下
# define ZEND_FASTCALL __attribute__((fastcall))
  • (关于双井号,它起连接符的作用,可以参考这里了解)
  • 在被预处理后,它的样子类似于下方所示:
void zif_implode(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC)
  • 也就是说 return_value 是作为整个 implode 扩展函数定义的一个形参
  • 在 php_implode 的定义中,一开始,先定义了一些即将用到的变量,随后使用 ALLOCA_FLAG(use_heap) 进行标识,如果申请内存,则申请的是堆内存
  • 通过 numelems = zend_hash_num_elements(Z_ARRVAL_P(pieces));
  • 처음 몇 줄은 매개변수 선언과 관련된 정보입니다. 여기서 *arg2는 조각 인수를 수신하기 위한 포인터입니다.
  • 아래 arg2의 판단에서 arg2가 비어있다면 조각에 해당하는 값이 전달되지 않았다는 의미입니다
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zend_array), tmp) {
    // ...
} ZEND_HASH_FOREACH_END();

조각 매개변수를 전달하지 않음

  • 을 통과하지 못했다는 판단에서 조각 매개변수, 즉arg2 == NULL, 주로 일부 매개변수 처리에 사용
      글루를 빈 문자열로 초기화하고 조각 변수에 전달된 유일한 매개변수를 할당한 다음 php_implode를 호출합니다. (glue,pieces,return_value);
    • 매우 중요한 php_implode

    • piece 매개변수의 전달 여부에 관계없이 매개변수를 처리한 후 PHPAPI의 관련 함수 php_implode가 이 함수에 핵심 로직이 모두 구현되어 있으니 자세히 살펴보겠습니다

      🎜php_implode를 호출하면 선언되지 않은 것처럼 보이는 변수 return_value가 나타납니다. 맞아요, 허공에 나타난 것 같아요🎜🎜Google에서 PHP 소스 코드의 return_value를 검색하여 답변. 🎜🎜이 변수는 매크로 PHP_FUNCTION과 함께 나타나는 것으로 나타났는데 여기서 implode의 구현은 PHP_FUNCTION(implode)를 통해 선언됩니다. PHP_FUNCTION의 정의는 다음과 같습니다. 🎜🎜
      struct {
          zend_string *str;
          zend_long    lval;
      } *strings, *ptr;
      🎜🎜(이중 파운드 기호에 대해 연결기 역할을 하며 여기를 참조하여 알아보세요.) 🎜🎜전처리 후 아래와 비슷하게 보입니다. 🎜🎜
      // tmp 是循环中的单元值
      ptr->str = Z_STR_P(tmp);
      len += ZSTR_LEN(ptr->str);
      ptr->lval = 0;
      ptr++;
      🎜🎜 즉, return_value는 implode 확장 함수 전체로 정의된 형식적인 매개변수로, php_implode 정의에서는 처음에 사용될 변수들을 먼저 정의한 뒤 ALLOCA_FLAG(use_heap)를 정의합니다. 식별에 사용되며, 메모리를 신청하면 힙 메모리🎜🎜를 신청하여 조각 매개변수의 단위 수를 가져옵니다. 🎜🎜여기에도 판단이 있습니다. 배열 셀의 개수가 1이면 유일한 셀이 문자열로 직접 반환됩니다. 🎜🎜마지막 단계는 다중 배열 단위의 상황을 처리하는 것입니다. 앞서 언급한 것처럼 메모리를 신청할 경우 힙 메모리를 신청하게 됩니다. 스택에 비해 힙 메모리는 효율성이 떨어지므로 사용하게 됩니다. 꼭 필요한 경우 힙 메모리를 적용하려면 다중 유닛 어레이의 상황이 필요합니다. 🎜🎜그런 다음 조각 루프의 경우 스플라이싱에 대한 값을 가져옵니다. 소스 코드의 foreach 루프는 다음과 같이 고정된 구조를 갖습니다. 🎜🎜
      while (val) {
          val /= 10;
          len++;
      }
      🎜🎜이 일반적인 작성 방법은 PHP 확장을 작성하는 데 필수적이라고 생각합니다. 아직 프로덕션 준비가 된 단일 PHP 확장을 작성하지는 않았지만. 하지만 나는 그 방향으로 가려고 노력 중이에요! 🎜🎜🎜루프 내에서 배열 단위는 세 가지 범주로 나뉩니다. 🎜
      • 字符串
      • 整形数据
      • 其它
    • 事实上,在循环开始之前,源码中,先申请了一块内存,用于存放下面的结构体,并且个数恰好是 pieces 数组单元的个数。
    struct {
        zend_string *str;
        zend_long    lval;
    } *strings, *ptr;
    • 可以看到,结构体成员包含 zend 字符串以及 zend 整形数据。这个结构体的出现,恰好是为了存放数组单元中的 zend 字符串/zend 整形数据。

    字符串

    • 先假设,pieces 数组单元中,都是字符串类型,此时循环中执行的逻辑就是:
    // tmp 是循环中的单元值
    ptr->str = Z_STR_P(tmp);
    len += ZSTR_LEN(ptr->str);
    ptr->lval = 0;
    ptr++;
    • 其中,tmp 是循环中的单元值。每经历一次循环,会将单元值放入结构体中,随后进行指针 +1 运算,指针就指向存储下一个结构体数据的地址:
    • 并且,在这期间,统计出了字符串的总长度 len += ZSTR_LEN(ptr->str);

    整数类型

    • 以上,讨论了数组单元中是字符串的情况。接下来看看,如果数组单元的类型是数值类型时会发生什么?
    • 判断一个变量是否是数值类型(其实是 zend_long),通用方法是:Z_TYPE_P(tmp) == IS_LONG。一旦知道当前的数据类型是 zend_long,则将其赋值给 ptr 的 lval 结构体成员。然后 ptr 指针后移一个单位长度。
    • 但是,我们知道我们不能像获取 zend_string 的长度一样去获取 zend_long 的字符长度。如果是 zend_string,则可以通过 len += ZSTR_LEN(val); 的方式获取其字符长度。对于 zend_long,有什么好的方法呢?
    • 在源码中是通过对 10 做除法运算,得出结果的一部分,再慢慢的累加其长度:
    while (val) {
        val /= 10;
        len++;
    }
    • 如果是负数呢?没有什么特别的办法,直接判断处理:
    if (val <h3 id="字符串的处理和拷贝">字符串的处理和拷贝</h3>
    • 循环结束后,ptr 就是指向这段内存的尾部的指针。
    • 然后,申请了一段内存:str = zend_string_safe_alloc(numelems - 1, ZSTR_LEN(glue), len, 0);,用于存放单元字符串总长度加上连接字符的总长度,即 (n-1)glue + len。因为 n 个数组单元,只需要 n-1 个 glue 字符串。然后,将这段内存的尾地址,赋值给 cptr,为什么要指向尾部呢?看下一部分,你就会明白了。
    • 接下来,需要循环取出存放在 ptr 中的字符。我们知道,ptr 此时是所处内存区域的尾部,为了能有序展示连接的字符串,源码中,是从后向前循环处理。这也就是为什么需要把 cptr 指向所在内存区域的尾部的原因。
    • 进入循环,先进行 ptr--;,然后针对 ptr->str 的判断 if (EXPECTED(ptr->str)),看了一下此处的 EXPECTED 的作用,可以参考这里。可以简单的将其理解一种汇编层面的优化,当实际执行的情况更偏向于当前条件下的分支而非 else 的分支时,就用 EXPECTED 宏将其包装起来:EXPECTED(ptr->str)。我敢说,当你调用 implode 传递的数组中都是数字而非字符串,那么这里的 EXPECTED 作用就会失效。
    • 接下来的两行是比较核心的:
    cptr -= ZSTR_LEN(ptr->str);
    memcpy(cptr, ZSTR_VAL(ptr->str), ZSTR_LEN(ptr->str));
    • cptr 的指针前移一个数组单元字符的长度,然后将 ptr->str (某数组单元的值)通过 c 标准库函数 memcpy 拷贝到 cptr 内存空间中。
    • ptr == strings 满足时,意味着 ptr 不再有可被复制的字符串/数字。因为 strings 是 ptr 所在区域的首地址。
    • 通过上面,已经成功将一个数组单元的字符串拷贝到 cptr 对应的内存区域中,接下来如何处理 glue 呢?
    • 只需要像处理 ptr->str 一样处理 glue 即可。至少源码中是这么做的。
    • 代码中有一段是:*cptr = 0,它的作用相当于赋值空字符串。
    • cptr 继续前移 glue 的长度,然后,将 glue 字符串拷贝到 cptr 对应的内存区域中。没错,还是用 memcpy 函数。
    • 到这里,第一次循环结束了。我应该不需要像实际循环中那样描述这里的循环吧?相信优秀的你,是完全可以参考上方的描述脑补出来的 ^^
    • 当然,处理返回的两句还是要提一下:
    free_alloca(strings, use_heap);
    RETURN_NEW_STR(str);
    • strings 的那一片内存空间只是存储临时值的,因此函数结束了,就必须跟 strings 说再见。我们知道 c 语言是手动管理内存的,没有 GC,你要显示的释放内存,即 free_alloca(strings, use_heap);
    • 在上面的描述中,我们只讲到了 cptr,但这里的返回值却是 str。
    • 不用怀疑,这里是对的,我们所讲的 cptr 那一片内存区域的首地址就是 str。并通过宏 RETURN_NEW_STR 会将最终的返回值写入 return_value 中

    实践

    • 为了可能更加清晰 implode 源码中代码运行时的情况,接下来,我们通过 PHP 扩展的方式对其进行 debug。在这个过程中的代码,我都放在 GitHub 的仓库中,分支名是 debug/implode,可自行下载运行,看看效果。
    • 新建 PHP 扩展模板的操作,可以参考这里。请确保操作完里面描述的步骤。
    • 接下来,主要针对 su_dd.c 文件修改代码。为了能通过修改代码来看效果,将 php_implode 函数复制到扩展文件中,并将其命名为 su_php_implode:
    static void su_php_implode(const zend_string *glue, zval *pieces, zval *return_value)
    {
        // 源码内容省略
    }
    • 在扩展中新增一个扩展函数 su_test:
    PHP_FUNCTION(su_test)
    {
        zval tmp;
        zend_string *str, *glue, *tmp_glue;
        zval *arg1, *arg2 = NULL, *pieces;
    
        ZEND_PARSE_PARAMETERS_START(1, 2)
            Z_PARAM_ZVAL(arg1)
            Z_PARAM_OPTIONAL
            Z_PARAM_ZVAL(arg2)
        ZEND_PARSE_PARAMETERS_END();
        glue = zval_get_tmp_string(arg1, &tmp_glue);
        pieces = arg2;
        su_php_implode(glue, pieces, return_value);
    }
    • 因为扩展的编译以及引入,前面的已经提及。因此,此时只需编写 PHP 代码进行调用:
    // t1.php
    $res = su_test('-', [
        2019, '01', '01',
    ]);
    var_dump($res);
    • PHP 运行该脚本,输出:string(10) "2019-01-01",这意味着,你已经成功编写了一个扩展函数。别急,这只是迈出了第一步,别忘记我们的目标:通过调试来学习 implode 源码。
    • 接下来,我们通过 gdb 工具,调试以上 PHP 代码在源码层面的运行。为了防止初学者不会用 gdb,这里就繁琐的写出这个过程。如果没有安装 gdb,请自行谷歌。
    • 先进入 PHP 脚本所在路径。命令行下:
    gdb php
    b zval_get_tmp_string
    r t1.php
    • b 即 break,表示打一个断点
    • r 即 run,表示运行脚本
    • s 即 step,表示一步一步调试,遇到方法调用,会进入方法内部单步调试
    • n 即 next,表示一行一行调试。遇到方法,则调试直接略过直接执行返回,调试不会进入其内部。
    • p 即 print,表示打印当前作用域中的一个变量
    • 当运行完 r t1.php,则会定位到第一个断点对应的行,显示如下:
    Breakpoint 1, zif_su_test (execute_data=0x7ffff1a1d0c0, 
        return_value=0x7ffff1a1d090)
        at /home/www/clang/php-7.3.3/ext/su_dd/su_dd.c:179
    179        glue = zval_get_tmp_string(arg1, &tmp_glue);
    • 此时,按下 n,显示如下:
    184        su_php_implode(glue, pieces, return_value);
    • 此时,当前的作用域中存在变量:gluepiecesreturn_value
    • 我们可以通过 gdb 调试,查看 pieces 的值。先使用命令:p pieces,此时在终端会显示类似于如下内容:
    $1 = (zval *) 0x7ffff1a1d120
    • 表明 pieces 是一个 zval 类型的指针,0x7ffff1a1d120 是其地址,当然,你运行的时候对应的也是一个地址,只不过跟我的这个会不太一样。
    • 我们继续使用 p 去打印存储于改地址的变量内容:p *$1,$1 可以认为是一个临时变量名,* 是取值运算符。运行完后,此时显示如下:
    (gdb) p *$1
    $2 = {value = {lval = 140737247576960, dval = 6.9533439118030153e-310, 
        counted = 0x7ffff1a60380, str = 0x7ffff1a60380, arr = 0x7ffff1a60380, 
        obj = 0x7ffff1a60380, res = 0x7ffff1a60380, ref = 0x7ffff1a60380, 
        ast = 0x7ffff1a60380, zv = 0x7ffff1a60380, ptr = 0x7ffff1a60380, 
        ce = 0x7ffff1a60380, func = 0x7ffff1a60380, ww = {w1 = 4054188928, 
          w2 = 32767}}, u1 = {v = {type = 7 '\a', type_flags = 1 '\001', u = {
            call_info = 0, extra = 0}}, type_info = 263}, u2 = {next = 0, 
        cache_slot = 0, opline_num = 0, lineno = 0, num_args = 0, fe_pos = 0, 
        fe_iter_idx = 0, access_flags = 0, property_guard = 0, constant_flags = 0, 
        extra = 0}}
    • 打印的内容,看起来是一堆乱糟糟的字符,这实际上是 zval 的结构体,其中的字段刚好是和 zval 的成员一一对应的,为了便于读者阅读,这里直接贴出 zval 的结构体信息:
    struct _zval_struct {
        zend_value        value;            /* value */
        union {
            struct {
                ZEND_ENDIAN_LOHI_3(
                    zend_uchar    type,            /* active type */
                    zend_uchar    type_flags,
                    union {
                        uint16_t  call_info;    /* call info for EX(This) */
                        uint16_t  extra;        /* not further specified */
                    } u)
            } v;
            uint32_t type_info;
        } u1;
        union {
            uint32_t     next;                 /* hash collision chain */
            uint32_t     cache_slot;           /* cache slot (for RECV_INIT) */
            uint32_t     opline_num;           /* opline number (for FAST_CALL) */
            uint32_t     lineno;               /* line number (for ast nodes) */
            uint32_t     num_args;             /* arguments number for EX(This) */
            uint32_t     fe_pos;               /* foreach position */
            uint32_t     fe_iter_idx;          /* foreach iterator index */
            uint32_t     access_flags;         /* class constant access flags */
            uint32_t     property_guard;       /* single property guard */
            uint32_t     constant_flags;       /* constant flags */
            uint32_t     extra;                /* not further specified */
        } u2;
    };
    • 我们直指要害 —— value,打印一下其中的内容。打印结构体成员可以使用 . 运算符,例如:p $2.value,运行这个命令,显示如下:
    (gdb) p $2.value
    $3 = {lval = 140737247576960, dval = 6.9533439118030153e-310, 
      counted = 0x7ffff1a60380, str = 0x7ffff1a60380, arr = 0x7ffff1a60380, 
      obj = 0x7ffff1a60380, res = 0x7ffff1a60380, ref = 0x7ffff1a60380, 
      ast = 0x7ffff1a60380, zv = 0x7ffff1a60380, ptr = 0x7ffff1a60380, 
      ce = 0x7ffff1a60380, func = 0x7ffff1a60380, ww = {w1 = 4054188928, 
        w2 = 32767}}
    • 通过 zval 结构体,我们知道 value 成员的类型是 zend_value,很不幸,这也是一个结构体:
    typedef union _zend_value {
        zend_long         lval;                /* long value */
        double            dval;                /* double value */
        zend_refcounted  *counted;
        zend_string      *str;
        zend_array       *arr;
        zend_object      *obj;
        zend_resource    *res;
        zend_reference   *ref;
        zend_ast_ref     *ast;
        zval             *zv;
        void             *ptr;
        zend_class_entry *ce;
        zend_function    *func;
        struct {
            uint32_t w1;
            uint32_t w2;
        } ww;
    } zend_value;
    • 我们要打印的变量是 pieces,我们知道它是一个数组,因而此时我们直接取 zend_value 结构体的 *arr 成员,它外表看起来就是一个指针,因此打印其内容,需要使用 * 运算符
    (gdb) p *$3.arr
    $4 = {gc = {refcount = 2, u = {type_info = 23}}, u = {v = {flags = 28 '\034', 
          _unused = 0 '\000', nIteratorsCount = 0 '\000', _unused2 = 0 '\000'}, 
        flags = 28}, nTableMask = 4294967294, arData = 0x7ffff1a67648, 
      nNumUsed = 3, nNumOfElements = 3, nTableSize = 8, nInternalPointer = 0, 
      nNextFreeElement = 3, pDestructor = 0x555555b6e200 <zval_ptr_dtor>}</zval_ptr_dtor>
    • 真棒!到目前为止,貌似一切都按照预定的路线进行。通过 zend_value 结构体,可以知道 *arr 的类型是 zend_array:
    struct _zend_array {
        zend_refcounted_h gc;
        union {
            struct {
                ZEND_ENDIAN_LOHI_4(
                    zend_uchar    flags,
                    zend_uchar    _unused,
                    zend_uchar    nIteratorsCount,
                    zend_uchar    _unused2)
            } v;
            uint32_t flags;
        } u;
        uint32_t          nTableMask;
        Bucket           *arData;
        uint32_t          nNumUsed;
        uint32_t          nNumOfElements;
        uint32_t          nTableSize;
        uint32_t          nInternalPointer;
        zend_long         nNextFreeElement;
        dtor_func_t       pDestructor;
    };
    • 了解 PHP 数组的同学一定知道它底层是一个 HashTable,感兴趣的同学,可以去自行了解一下 HashTable。这里,我们打印 *arData,使用:p *$4.arDaa:
    (gdb) p *$4.arData
    $5 = {val = {value = {lval = 2019, dval = 9.9751853895347677e-321, 
          counted = 0x7e3, str = 0x7e3, arr = 0x7e3, obj = 0x7e3, res = 0x7e3, 
          ref = 0x7e3, ast = 0x7e3, zv = 0x7e3, ptr = 0x7e3, ce = 0x7e3, 
          func = 0x7e3, ww = {w1 = 2019, w2 = 0}}, u1 = {v = {type = 4 '\004', 
            type_flags = 0 '\000', u = {call_info = 0, extra = 0}}, type_info = 4}, 
        u2 = {next = 0, cache_slot = 0, opline_num = 0, lineno = 0, num_args = 0, 
          fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, 
          constant_flags = 0, extra = 0}}, h = 0, key = 0x0}
    • 到这里,我们已经可以看到 pieces 数组第一个单元的值 —— 2019,就是那段 lval = 2019
    • 好了,关于 gdb 的简单使用就先介绍到这里。文章开篇,我们提到,如果数组是多维数组,会发生什么?我们实践的主要目标就是简单实现二维数组的 implode
    • 在 PHP 的 implode 函数中,如果是多维数组,则会直接把里层的数组显示为 Array 字符串。
    $res = implode('-', [
        2019, '01', '01', [1,2]
    ]);
    var_dump($res);
    • 运行这段脚本,会输出如下:
    PHP Notice:  Array to string conversion in /path/to/t2.php on line 3
    PHP Notice:  Array to string conversion in /path/to/t2.php on line 3
    string(16) "2019-01-01-Array"
    • 为了能够支持连接数组,我们需要改写 php_implode,因此,先拷贝一下 php_implode 到写扩展代码的文件中:
    PHPAPI void php_implode(const zend_string *glue, zval *pieces, zval *return_value)
    {
        zval         *tmp;
        int           numelems;
        zend_string  *str;
        char         *cptr;
        size_t        len = 0;
        struct {
            zend_string *str;
            zend_long    lval;
        } *strings, *ptr;
        ALLOCA_FLAG(use_heap)
    
        numelems = zend_hash_num_elements(Z_ARRVAL_P(pieces));
    
        if (numelems == 0) {
            RETURN_EMPTY_STRING();
        } else if (numelems == 1) {
            /* loop to search the first not undefined element... */
            ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pieces), tmp) {
                RETURN_STR(zval_get_string(tmp));
            } ZEND_HASH_FOREACH_END();
        }
    
        ptr = strings = do_alloca((sizeof(*strings)) * numelems, use_heap);
    
        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pieces), tmp) {
            if (EXPECTED(Z_TYPE_P(tmp) == IS_STRING)) {
                ptr->str = Z_STR_P(tmp);
                len += ZSTR_LEN(ptr->str);
                ptr->lval = 0;
                ptr++;
            } else if (UNEXPECTED(Z_TYPE_P(tmp) == IS_LONG)) {
                zend_long val = Z_LVAL_P(tmp);
    
                ptr->str = NULL;
                ptr->lval = val;
                ptr++;
                if (val str = zval_get_string_func(tmp);
                len += ZSTR_LEN(ptr->str);
                ptr->lval = 1;
                ptr++;
            }
        } ZEND_HASH_FOREACH_END();
    
        /* numelems can not be 0, we checked above */
        str = zend_string_safe_alloc(numelems - 1, ZSTR_LEN(glue), len, 0);
        cptr = ZSTR_VAL(str) + ZSTR_LEN(str);
        *cptr = 0;
    
        while (1) {
            ptr--;
            if (EXPECTED(ptr->str)) {
                cptr -= ZSTR_LEN(ptr->str);
                memcpy(cptr, ZSTR_VAL(ptr->str), ZSTR_LEN(ptr->str));
                if (ptr->lval) {
                    zend_string_release_ex(ptr->str, 0);
                }
            } else {
                char *oldPtr = cptr;
                char oldVal = *cptr;
                cptr = zend_print_long_to_buf(cptr, ptr->lval);
                *oldPtr = oldVal;
            }
    
            if (ptr == strings) {
                break;
            }
    
            cptr -= ZSTR_LEN(glue);
            memcpy(cptr, ZSTR_VAL(glue), ZSTR_LEN(glue));
        }
    
        free_alloca(strings, use_heap);
        RETURN_NEW_STR(str);
    }
    • 先将函数签名稍微调整成 static void su_php_implode(const zend_string *glue, zval *pieces, zval *return_value)
    • 我们可以看到其中有一段循环 pieces 的处理:
    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pieces), tmp) {
            if (EXPECTED(Z_TYPE_P(tmp) == IS_STRING)) {
                // ...
            } else if (UNEXPECTED(Z_TYPE_P(tmp) == IS_LONG)) {
                // ...
            } else {
                // ...
            }
        } ZEND_HASH_FOREACH_END();
    • 我们只需将其中的 if 分支新增一个分支:else if (UNEXPECTED(Z_TYPE_P(tmp) == IS_ARRAY)),其具体内容如下:
    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pieces), tmp) {
        if (EXPECTED(Z_TYPE_P(tmp) == IS_STRING)) {
            // ...
        } else if (UNEXPECTED(Z_TYPE_P(tmp) == IS_LONG)) {
            // ...
        } else if (UNEXPECTED(Z_TYPE_P(tmp) == IS_ARRAY)) {
            // 如果值是数组,则调用 php_implode,将其使用 glue 连接成字符串
            cptr = ZSTR_VAL(ptr->str);
            zend_string* str2 = origin_php_implode(glue, tmp, tmp_val);
            ptr->str = str2;
            // 此时,要拿到拼接后的字符串长度
            len += ZSTR_LEN(str2);
            ptr++;
        } else {
            // ...
        }
    } ZEND_HASH_FOREACH_END();
    • 正如注释中写的,当遇到数组的单元是数组类型时,我们会调用原先的 php_implode,只不过,这个“php_implode”会真的返回一个 zend_string 指针,在此我将其改名为 origin_php_implode
    static zend_string* origin_php_implode(const zend_string *glue, zval *pieces, zval *return_value)
    {
        zval         *tmp;
        int           numelems;
        zend_string  *str;
        char         *cptr;
        size_t        len = 0;
        struct {
            zend_string *str;
            zend_long    lval;
        } *strings, *ptr;
        ALLOCA_FLAG(use_heap)
    
        numelems = zend_hash_num_elements(Z_ARRVAL_P(pieces));
    
        if (numelems == 0) {
            RETURN_EMPTY_STRING();
        } else if (numelems == 1) {
            /* loop to search the first not undefined element... */
            ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pieces), tmp) {
                RETURN_STR(zval_get_string(tmp));
            } ZEND_HASH_FOREACH_END();
        }
    
        ptr = strings = do_alloca((sizeof(*strings)) * numelems, use_heap);
    
        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pieces), tmp) {
            if (EXPECTED(Z_TYPE_P(tmp) == IS_STRING)) {
                ptr->str = Z_STR_P(tmp);
                len += ZSTR_LEN(ptr->str);
                ptr->lval = 0;
                ptr++;
            } else if (UNEXPECTED(Z_TYPE_P(tmp) == IS_LONG)) {
                zend_long val = Z_LVAL_P(tmp);
    
                ptr->str = NULL;
                ptr->lval = val;
                ptr++;
                if (val str = zval_get_string_func(tmp);
                len += ZSTR_LEN(ptr->str);
                ptr->lval = 1;
                ptr++;
            }
        } ZEND_HASH_FOREACH_END();
    
        /* numelems can not be 0, we checked above */
        str = zend_string_safe_alloc(numelems - 1, ZSTR_LEN(glue), len, 0);
        cptr = ZSTR_VAL(str) + ZSTR_LEN(str);
        *cptr = 0;
    
        while (1) {
            ptr--;
            if (EXPECTED(ptr->str)) {
                cptr -= ZSTR_LEN(ptr->str);
                memcpy(cptr, ZSTR_VAL(ptr->str), ZSTR_LEN(ptr->str));
                if (ptr->lval) {
                    zend_string_release_ex(ptr->str, 0);
                }
            } else {
                char *oldPtr = cptr;
                char oldVal = *cptr;
                cptr = zend_print_long_to_buf(cptr, ptr->lval);
                *oldPtr = oldVal;
            }
    
            if (ptr == strings) {
                break;
            }
    
            cptr -= ZSTR_LEN(glue);
            memcpy(cptr, ZSTR_VAL(glue), ZSTR_LEN(glue));
        }
    
        free_alloca(strings, use_heap);
        // RETURN_NEW_STR(str);
        return str;
    }
    • 内容大体不变,只有函数签名以及返回值的地方略作调整了。
    • 配合前面的 PHP_FUNCTION(su_test),功能实现的差不多了。我们去编译看看:
    ./configure
    sudo make
    sudo make install
    • 太棒了,编译通过。我们去执行一下 PHP 脚本:
    $res = su_test('-', [
        2019, '01', '01', ['1', '2',],
    ]);
    var_dump($res);
    • 输出如下:
    string(14) "2019-01-01-1-2"
    • 恭喜,我们已经大功告成!

    위 내용은 PHP 소스 코드 - 내부 함수 소스 코드 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

  • 성명
    본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
    11 최고의 PHP URL 쇼트너 스크립트 (무료 및 프리미엄)11 최고의 PHP URL 쇼트너 스크립트 (무료 및 프리미엄)Mar 03, 2025 am 10:49 AM

    종종 키워드와 추적 매개 변수로 혼란스러워하는 긴 URL은 방문자를 방해 할 수 있습니다. URL 단축 스크립트는 솔루션을 제공하여 소셜 미디어 및 기타 플랫폼에 이상적인 간결한 링크를 만듭니다. 이 스크립트는 개별 웹 사이트 a에 유용합니다

    Instagram API 소개Instagram API 소개Mar 02, 2025 am 09:32 AM

    Instagram은 2012 년 Facebook에서 유명한 인수에 이어 타사 사용을 위해 두 개의 API 세트를 채택했습니다. Instagram Graph API 및 Instagram Basic Display API입니다. 개발자는

    Laravel의 플래시 세션 데이터로 작업합니다Laravel의 플래시 세션 데이터로 작업합니다Mar 12, 2025 pm 05:08 PM

    Laravel은 직관적 인 플래시 방법을 사용하여 임시 세션 데이터 처리를 단순화합니다. 응용 프로그램에 간단한 메시지, 경고 또는 알림을 표시하는 데 적합합니다. 데이터는 기본적으로 후속 요청에만 지속됩니다. $ 요청-

    Laravel Back End : Part 2, React가있는 React 앱 구축Laravel Back End : Part 2, React가있는 React 앱 구축Mar 04, 2025 am 09:33 AM

    이것은 Laravel 백엔드가있는 React Application을 구축하는 데있어 시리즈의 두 번째이자 마지막 부분입니다. 이 시리즈의 첫 번째 부분에서는 기본 제품 목록 응용 프로그램을 위해 Laravel을 사용하여 편안한 API를 만들었습니다. 이 튜토리얼에서는 Dev가 될 것입니다

    Laravel 테스트에서 단순화 된 HTTP 응답 조롱Laravel 테스트에서 단순화 된 HTTP 응답 조롱Mar 12, 2025 pm 05:09 PM

    Laravel은 간결한 HTTP 응답 시뮬레이션 구문을 제공하여 HTTP 상호 작용 테스트를 단순화합니다. 이 접근법은 테스트 시뮬레이션을보다 직관적으로 만들면서 코드 중복성을 크게 줄입니다. 기본 구현은 다양한 응답 유형 단축키를 제공합니다. Illuminate \ support \ Facades \ http를 사용하십시오. http :: 가짜 ([ 'google.com'=> ​​'Hello World', 'github.com'=> ​​[ 'foo'=> 'bar'], 'forge.laravel.com'=>

    PHP의 컬 : REST API에서 PHP Curl Extension 사용 방법PHP의 컬 : REST API에서 PHP Curl Extension 사용 방법Mar 14, 2025 am 11:42 AM

    PHP 클라이언트 URL (CURL) 확장자는 개발자를위한 강력한 도구이며 원격 서버 및 REST API와의 원활한 상호 작용을 가능하게합니다. PHP CURL은 존경받는 다중 프로모토콜 파일 전송 라이브러리 인 Libcurl을 활용하여 효율적인 execu를 용이하게합니다.

    Codecanyon에서 12 개의 최고의 PHP 채팅 스크립트Codecanyon에서 12 개의 최고의 PHP 채팅 스크립트Mar 13, 2025 pm 12:08 PM

    고객의 가장 긴급한 문제에 실시간 인스턴트 솔루션을 제공하고 싶습니까? 라이브 채팅을 통해 고객과 실시간 대화를 나누고 문제를 즉시 해결할 수 있습니다. 그것은 당신이 당신의 관습에 더 빠른 서비스를 제공 할 수 있도록합니다.

    2025 PHP 상황 조사 발표2025 PHP 상황 조사 발표Mar 03, 2025 pm 04:20 PM

    2025 PHP Landscape Survey는 현재 PHP 개발 동향을 조사합니다. 개발자와 비즈니스에 대한 통찰력을 제공하는 프레임 워크 사용, 배포 방법 및 과제를 탐색합니다. 이 조사는 현대 PHP Versio의 성장을 예상합니다

    See all articles

    핫 AI 도구

    Undresser.AI Undress

    Undresser.AI Undress

    사실적인 누드 사진을 만들기 위한 AI 기반 앱

    AI Clothes Remover

    AI Clothes Remover

    사진에서 옷을 제거하는 온라인 AI 도구입니다.

    Undress AI Tool

    Undress AI Tool

    무료로 이미지를 벗다

    Clothoff.io

    Clothoff.io

    AI 옷 제거제

    AI Hentai Generator

    AI Hentai Generator

    AI Hentai를 무료로 생성하십시오.

    뜨거운 도구

    안전한 시험 브라우저

    안전한 시험 브라우저

    안전한 시험 브라우저는 온라인 시험을 안전하게 치르기 위한 보안 브라우저 환경입니다. 이 소프트웨어는 모든 컴퓨터를 안전한 워크스테이션으로 바꿔줍니다. 이는 모든 유틸리티에 대한 액세스를 제어하고 학생들이 승인되지 않은 리소스를 사용하는 것을 방지합니다.

    DVWA

    DVWA

    DVWA(Damn Vulnerable Web App)는 매우 취약한 PHP/MySQL 웹 애플리케이션입니다. 주요 목표는 보안 전문가가 법적 환경에서 자신의 기술과 도구를 테스트하고, 웹 개발자가 웹 응용 프로그램 보안 프로세스를 더 잘 이해할 수 있도록 돕고, 교사/학생이 교실 환경 웹 응용 프로그램에서 가르치고 배울 수 있도록 돕는 것입니다. 보안. DVWA의 목표는 다양한 난이도의 간단하고 간단한 인터페이스를 통해 가장 일반적인 웹 취약점 중 일부를 연습하는 것입니다. 이 소프트웨어는

    SublimeText3 영어 버전

    SublimeText3 영어 버전

    권장 사항: Win 버전, 코드 프롬프트 지원!

    에디트플러스 중국어 크랙 버전

    에디트플러스 중국어 크랙 버전

    작은 크기, 구문 강조, 코드 프롬프트 기능을 지원하지 않음

    SublimeText3 Linux 새 버전

    SublimeText3 Linux 새 버전

    SublimeText3 Linux 최신 버전