PHP-Quellcode – Analyse des Funktionsquellcodes implodieren

步履不停
Freigeben: 2023-04-06 22:38:01
Original
4397 Leute haben es durchsucht

PHP-Quellcode – Analyse des Funktionsquellcodes implodieren

implode in PHP

  • In PHP besteht die Funktion von implode darin, den Wert eines eindimensionalen Arrays in einen String umzuwandeln. Erinnern Sie sich an ein eindimensionales Array. Was passiert, wenn es mehrdimensional ist? In dieser Analyse wird darauf eingegangen.
  • Tatsächlich können wir aus der offiziellen Dokumentation wissen, dass Implode zwei Verwendungszwecke hat, was aus der Funktionssignatur ersichtlich ist:
// 方法1
implode ( string $glue , array $pieces ) : string
// 方法2
implode ( array $pieces ) : string
Nach dem Login kopieren
  • Weil, wenn Kleber ist nicht übergeben Manchmal verwendet die interne Implementierung standardmäßig eine leere Zeichenfolge.
  • Wie aus einem einfachen Beispiel ersichtlich ist:
$pieces = [
    123,
    ',是一个',
    'number!',
];
$str1 = implode($pieces);
$str2 = implode('', $pieces);

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

Implementierung des Quellcodes

  • Sie können die Funktionsdefinition finden, indem Sie nach dem Schlüsselwort Die ersten Zeilen von PHP_FUNCTION(implode)extstandardstring.c in Zeile 1288 von in der Datei
  • enthalten Informationen zur Parameterdeklaration. Dabei ist *arg2 ein Zeiger auf den Empfang des Arguments „pieces“.
  • Wenn arg2 unten leer ist, bedeutet dies, dass der den Stücken entsprechende Wert nicht übergeben wird
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;
    }
}
Nach dem Login kopieren
Der Parameter „Stücke“ wird nicht übergeben

    Bei der Beurteilung, dass der Parameter „pieces“ nicht übergeben wird, ist
  • hauptsächlich eine Verarbeitung der Parameter erforderlich. arg2 == NULL
  • initialisiert den Kleber mit einer leeren Zeichenfolge und weist den einzigen übergebenen Parameter zu die Variable „pieces“ und rufen Sie dann einfach
  • php_implode(glue, pieces, return_value);
das sehr kritische php_implode

    auf. Unabhängig davon, ob der Parameter „pieces“ übergeben wird oder nicht, wird nach der Verarbeitung der Parameter die zugehörige Funktion aufgerufen php_implode von PHPAPI wird schließlich aufgerufen. Wie Sie sehen können, ist die gesamte Schlüssellogik in dieser Funktion implementiert. Schauen wir uns das also genauer an.
  • Beim Aufruf von php_implode erscheint eine Variable return_value, die nicht deklariert zu sein scheint . Genau, es schien aus dem Nichts zu kommen.
  • Habe bei Google gesucht
  • und die PHP源码中 return_valueAntwort gefunden.
  • Es stellt sich heraus, dass diese Variable mit dem Makro PHP_FUNCTION erscheint und die Implementierung von implode hier durch
  • deklariert wird. Die Definition von PHP_FUNCTION lautet: PHP_FUNCTION(implode)
#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))
Nach dem Login kopieren
    (Das doppelte Nummernzeichen dient als Konnektor, Sie können
  • hier nachschlagen , um mehr darüber zu erfahren)
  • in Nach der Vorverarbeitung sieht es wie folgt aus:
void zif_implode(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC)
Nach dem Login kopieren
    Das heißt, return_value ist ein formaler Parameter, der als die gesamte Implode-Erweiterungsfunktion definiert ist
  • in php_implode In der Definition von werden zunächst einige Variablen definiert, die verwendet werden sollen, und dann mit
  • markiert. Wenn Sie Speicher beantragen, beantragen Sie Heap-Speicher ALLOCA_FLAG(use_heap)
  • Holen Sie sich die Einheit des Stückparameters durch
  • Menge, wenn es sich um ein leeres Array handelt, wird die leere Zeichenfolge direkt zurückgegeben numelems = zend_hash_num_elements(Z_ARRVAL_P(pieces));
  • Hier gibt es auch eine Beurteilung. Wenn die Anzahl der Array-Zellen 1 ist, wird die einzige Zelle zurückgegeben direkt als String zurückgegeben werden.
  • Der letzte Schritt besteht darin, sich mit der Situation mehrerer Array-Einheiten zu befassen. Wenn Sie Speicher beantragen, ist Heap-Speicher im Vergleich zum Stapel weniger effizient wird nur in absolut notwendigen Situationen verwendet. Als nächstes wird Heap-Speicher beantragt, und die Situation ist die eines Arrays mit mehreren Einheiten.
  • Ermitteln Sie dann für die Pieces-Schleife ihren Wert zum Spleißen. Die foreach-Schleife im Quellcode hat eine feste Struktur wie folgt:
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zend_array), tmp) {
    // ...
} ZEND_HASH_FOREACH_END();
Nach dem Login kopieren
    Ich denke das ist eine gängige Schreibweise. Sie ist beim Schreiben von PHP-Erweiterungen unerlässlich. Obwohl ich noch keine einzige produktionsreife PHP-Erweiterung geschrieben habe. Aber ich versuche, in diese Richtung zu gehen!
  • Innerhalb der Schleife sind die Array-Einheiten in drei Kategorien unterteilt:

    • 字符串
    • 整形数据
    • 其它
  • 事实上,在循环开始之前,源码中,先申请了一块内存,用于存放下面的结构体,并且个数恰好是 pieces 数组单元的个数。
struct {
    zend_string *str;
    zend_long    lval;
} *strings, *ptr;
Nach dem Login kopieren
  • 可以看到,结构体成员包含 zend 字符串以及 zend 整形数据。这个结构体的出现,恰好是为了存放数组单元中的 zend 字符串/zend 整形数据。

字符串

  • 先假设,pieces 数组单元中,都是字符串类型,此时循环中执行的逻辑就是:
// tmp 是循环中的单元值
ptr->str = Z_STR_P(tmp);
len += ZSTR_LEN(ptr->str);
ptr->lval = 0;
ptr++;
Nach dem Login kopieren
  • 其中,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++;
}
Nach dem Login kopieren
  • 如果是负数呢?没有什么特别的办法,直接判断处理:
if (val <= 0) {
    len++;
}
Nach dem Login kopieren

字符串的处理和拷贝

  • 循环结束后,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));
    Nach dem Login kopieren
    • 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);
    Nach dem Login kopieren
    • 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)
    {
        // 源码内容省略
    }
    Nach dem Login kopieren
    • 在扩展中新增一个扩展函数 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);
    }
    Nach dem Login kopieren
    • 因为扩展的编译以及引入,前面的已经提及。因此,此时只需编写 PHP 代码进行调用:
    // t1.php
    $res = su_test('-', [
        2019, '01', '01',
    ]);
    var_dump($res);
    Nach dem Login kopieren
    • PHP 运行该脚本,输出:string(10) "2019-01-01",这意味着,你已经成功编写了一个扩展函数。别急,这只是迈出了第一步,别忘记我们的目标:通过调试来学习 implode 源码。
    • 接下来,我们通过 gdb 工具,调试以上 PHP 代码在源码层面的运行。为了防止初学者不会用 gdb,这里就繁琐的写出这个过程。如果没有安装 gdb,请自行谷歌。
    • 先进入 PHP 脚本所在路径。命令行下:
    gdb php
    b zval_get_tmp_string
    r t1.php
    Nach dem Login kopieren
    • 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);
    Nach dem Login kopieren
    • 此时,按下 n,显示如下:
    184        su_php_implode(glue, pieces, return_value);
    Nach dem Login kopieren
    • 此时,当前的作用域中存在变量:gluepiecesreturn_value
    • 我们可以通过 gdb 调试,查看 pieces 的值。先使用命令:p pieces,此时在终端会显示类似于如下内容:
    $1 = (zval *) 0x7ffff1a1d120
    Nach dem Login kopieren
    • 表明 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}}
    Nach dem Login kopieren
    • 打印的内容,看起来是一堆乱糟糟的字符,这实际上是 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;
    };
    Nach dem Login kopieren
    • 我们直指要害 —— 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}}
    Nach dem Login kopieren
    • 通过 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;
    Nach dem Login kopieren
    • 我们要打印的变量是 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>}
    Nach dem Login kopieren
    • 真棒!到目前为止,貌似一切都按照预定的路线进行。通过 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;
    };
    Nach dem Login kopieren
    • 了解 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}
    Nach dem Login kopieren
    • 到这里,我们已经可以看到 pieces 数组第一个单元的值 —— 2019,就是那段 lval = 2019
    • 好了,关于 gdb 的简单使用就先介绍到这里。文章开篇,我们提到,如果数组是多维数组,会发生什么?我们实践的主要目标就是简单实现二维数组的 implode
    • 在 PHP 的 implode 函数中,如果是多维数组,则会直接把里层的数组显示为 Array 字符串。
    $res = implode('-', [
        2019, '01', '01', [1,2]
    ]);
    var_dump($res);
    Nach dem Login kopieren
    • 运行这段脚本,会输出如下:
    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"
    Nach dem Login kopieren
    • 为了能够支持连接数组,我们需要改写 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 <= 0) {
                    len++;
                }
                while (val) {
                    val /= 10;
                    len++;
                }
            } else {
                ptr->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);
    }
    Nach dem Login kopieren
    • 先将函数签名稍微调整成 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();
    Nach dem Login kopieren
    • 我们只需将其中的 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();
    Nach dem Login kopieren
    • 正如注释中写的,当遇到数组的单元是数组类型时,我们会调用原先的 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 <= 0) {
                    len++;
                }
                while (val) {
                    val /= 10;
                    len++;
                }
            } else {
                ptr->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;
    }
    Nach dem Login kopieren
    • 内容大体不变,只有函数签名以及返回值的地方略作调整了。
    • 配合前面的 PHP_FUNCTION(su_test),功能实现的差不多了。我们去编译看看:
    ./configure
    sudo make
    sudo make install
    Nach dem Login kopieren
    • 太棒了,编译通过。我们去执行一下 PHP 脚本:
    $res = su_test('-', [
        2019, '01', '01', ['1', '2',],
    ]);
    var_dump($res);
    Nach dem Login kopieren
    • 输出如下:
    string(14) "2019-01-01-1-2"
    Nach dem Login kopieren
    • 恭喜,我们已经大功告成!

    Das obige ist der detaillierte Inhalt vonPHP-Quellcode – Analyse des Funktionsquellcodes implodieren. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage