这篇文章将为大家详细讲解有关PHP源码之implode函数源码怎么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。
// 方法1
implode ( string $glue , array $pieces ) : string
// 方法2
implode ( array $pieces ) : string
$pieces = [
123,
',是一个',
'number!',
];
$str1 = implode($pieces);
$str2 = implode('', $pieces);
var_dump($str1, $str2);
/*
string(20) "123,是一个number!"
string(20) "123,是一个number!"
*/
PHP_FUNCTION(implode)
可以找到,该函数定义于 \ext\standard\string.c
文件中的 1288 行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;
}
}
arg2 == NULL
,主要是对参数的一些处理php_implode(glue, pieces, return_value);
PHP源码中 return_value
,找到了答案。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)
ALLOCA_FLAG(use_heap)
进行标识,如果申请内存,则申请的是堆内存numelems = zend_hash_num_elements(Z_ARRVAL_P(pieces));
获取 pieces 参数的单元数量,如果是空数组,则直接返回空字符串ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zend_array), tmp) {
// ...
} ZEND_HASH_FOREACH_END();
在循环内,对数组单元分为三类:
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++;
len += ZSTR_LEN(ptr->str);
Z_TYPE_P(tmp) == IS_LONG
。一旦知道当前的数据类型是 zend_long,则将其赋值给 ptr 的 lval 结构体成员。然后 ptr 指针后移一个单位长度。len += ZSTR_LEN(val);
的方式获取其字符长度。对于 zend_long,有什么好的方法呢?while (val) {
val /= 10;
len++;
}
if (val <= 0) {
len++;
}
str = zend_string_safe_alloc(numelems - 1, ZSTR_LEN(glue), len, 0);
,用于存放单元字符串总长度加上连接字符的总长度,即 (n-1)glue + len
。因为 n 个数组单元,只需要 n-1 个 glue 字符串。然后,将这段内存的尾地址,赋值给 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));
ptr->str
(某数组单元的值)通过 c 标准库函数 memcpy 拷贝到 cptr 内存空间中。ptr == strings
满足时,意味着 ptr 不再有可被复制的字符串/数字。因为 strings 是 ptr 所在区域的首地址。ptr->str
一样处理 glue 即可。至少源码中是这么做的。*cptr = 0
,它的作用相当于赋值空字符串。free_alloca(strings, use_heap);
RETURN_NEW_STR(str);
free_alloca(strings, use_heap);
。RETURN_NEW_STR
会将最终的返回值写入 return_value 中debug/implode
,可自行下载运行,看看效果。static void su_php_implode(const zend_string *glue, zval *pieces, zval *return_value)
{
// 源码内容省略
}
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);
}
// t1.php
$res = su_test('-', [
2019, '01', '01',
]);
var_dump($res);
string(10) "2019-01-01"
,这意味着,你已经成功编写了一个扩展函数。别急,这只是迈出了第一步,别忘记我们的目标:通过调试来学习 implode 源码。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);
glue
,pieces
,return_value
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}}
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}}
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;
*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>}
*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;
};
*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}
lval = 2019
。$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"
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);
}
static void su_php_implode(const zend_string *glue, zval *pieces, zval *return_value)
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();
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();
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;
}
PHP_FUNCTION(su_test)
,功能实现的差不多了。我们去编译看看:./configure
sudo make
sudo make install
$res = su_test('-', [
2019, '01', '01', ['1', '2',],
]);
var_dump($res);
string(14) "2019-01-01-1-2"
关于PHP源码之implode函数源码怎么用就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。