寻根究底
最近看到一些 php 的函数时,一直在想,它背后是怎么实现的呢?不仔细的挖一遍它的底细,压根就是经常会用错。而且感觉用的是一个黑箱子。指不定里面啥时候窜出一条蛇来。所以,找来源码,推敲一下底层的实现逻辑。
第一节: 伸展运动 strpos
它的整个实现风貌先展现出来, 长的是貌美如花:
strpos 源码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51PHP_FUNCTION(strpos)
{
zval *needle;
zend_string *haystack;
char *found = NULL;
char needle_char[2];
zend_long offset = 0;
ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_STR(haystack)
Z_PARAM_ZVAL(needle)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(offset)
ZEND_PARSE_PARAMETERS_END();
if (offset < 0) {
offset += (zend_long)ZSTR_LEN(haystack);
}
if (offset < 0 || (size_t)offset > ZSTR_LEN(haystack)) {
php_error_docref(NULL, E_WARNING, "Offset not contained in string");
RETURN_FALSE;
}
if (Z_TYPE_P(needle) == IS_STRING) {
if (!Z_STRLEN_P(needle)) {
php_error_docref(NULL, E_WARNING, "Empty needle");
RETURN_FALSE;
}
found = (char*)php_memnstr(ZSTR_VAL(haystack) + offset,
Z_STRVAL_P(needle),
Z_STRLEN_P(needle),
ZSTR_VAL(haystack) + ZSTR_LEN(haystack));
} else {
if (php_needle_char(needle, needle_char) != SUCCESS) {
RETURN_FALSE;
}
needle_char[1] = 0;
found = (char*)php_memnstr(ZSTR_VAL(haystack) + offset,
needle_char,
1,
ZSTR_VAL(haystack) + ZSTR_LEN(haystack));
}
if (found) {
RETURN_LONG(found - ZSTR_VAL(haystack));
} else {
RETURN_FALSE;
}
}
初探
咋看,果然没有让我失望, 有点蒙: 这都啥啥啥? 第一眼只看懂了 if, else。 没系统的学习过 c 语言,阅读起来真的很费眼力劲, 呵呵。
那就先过一遍:1
2
3
4
5zval *needle
zend_string *haystack
char *found = NULL
char needle_char[2]
zend_long offset = 0
这一堆,是定义了一些变量。
然后1
2
3
4
5
6ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_STR(haystack)
Z_PARAM_ZVAL(needle)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(offset)
ZEND_PARSE_PARAMETERS_END()
没看懂,应该是初始化参数, (2, 3)说明是这个 strpos 函数的参数在 2 - 3 个之间。
接着,进行至第三个参数 offset 的判断了。1
2
3
4
5
6
7if (offset < 0) {
offset += (zend_long)ZSTR_LEN(haystack);
}
if (offset < 0 || (size_t)offset > ZSTR_LEN(haystack)) {
php_error_docref(NULL, E_WARNING, "Offset not contained in string");
RETURN_FALSE;
}
猜测当第三个参数 offset 是负数的时候,是反向的查找的。如果负数太小了,比如反向查找 ‘test’ 中的 ‘s’ 来说, 就是 strpos(‘test’, ‘t’, -10), 就会报错了。1
2
3
4➜ php -r "echo strpos('test', 's', -10);"
PHP Warning: strpos(): Offset not contained in string in Command line code on line 1
Warning: strpos(): Offset not contained in string in Command line code on line 1
果然。
先不追究细节, 走着。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21if (Z_TYPE_P(needle) == IS_STRING) {
if (!Z_STRLEN_P(needle)) {
php_error_docref(NULL, E_WARNING, "Empty needle");
RETURN_FALSE;
}
found = (char*)php_memnstr(ZSTR_VAL(haystack) + offset,
Z_STRVAL_P(needle),
Z_STRLEN_P(needle),
ZSTR_VAL(haystack) + ZSTR_LEN(haystack));
} else {
if (php_needle_char(needle, needle_char) != SUCCESS) {
RETURN_FALSE;
}
needle_char[1] = 0;
found = (char*)php_memnstr(ZSTR_VAL(haystack) + offset,
needle_char,
1,
ZSTR_VAL(haystack) + ZSTR_LEN(haystack));
}
一个 if(){} else{}, 哎呀,我滴亲娘啊,好像很简单啊,就判断一下要查找的参数 needle 是不是字符串类型, 然后如此如此, 最后返回一个 found。
而 found 最核心的就是一个真正查找这个字符的位置的函数 php_memnstr, 这个函数的实体实际是 zend_memnstr 这个函数。
继续追下去看看1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41static zend_always_inline const char *
zend_memnstr(const char *haystack, const char *needle, size_t needle_len, const char *end)
{
const char *p = haystack;
const char ne = needle[needle_len-1];
ptrdiff_t off_p;
size_t off_s;
if (needle_len == 1) {
return (const char *)memchr(p, *needle, (end-p));
}
off_p = end - haystack;
off_s = (off_p > 0) ? (size_t)off_p : 0;
if (needle_len > off_s) {
return NULL;
}
if (EXPECTED(off_s < 1024 || needle_len < 9)) {/* glibc memchr is faster when needle is too short */
end -= needle_len;
while (p <= end) {
if ((p = (const char *)memchr(p, *needle, (end-p+1))) && ne == p[needle_len-1]) {
if (!memcmp(needle, p, needle_len-1)) {
return p;
}
}
if (p == NULL) {
return NULL;
}
p++;
}
return NULL;
} else {
return zend_memnstr_ex(haystack, needle, needle_len, end);
}
}
这里 zend_memnstr() , 接收四个传进来的参数。 这里可以看到, 当判断出查找的字符串很短,查找的区间也很短时,(为啥是off_s < 1024 或者 needle_len < 9 这两个阀值, 不得而知) 调用的是 glibc 库,这个库是 linux 最底层的 api, 否则就跑去调用 zend_operators.h 文件下面的 ZEND_FASTCALL 类型的 zend_memnstr_ex, 注释里说 glibc 更快。
ptrdiff_t 这个其实是一个 zend_long 在/intl/collator/collator_sort.c文件下面有这个定义。1
2
3typedef zend_long ptrdiff_t;
#endif
又跳去搞事情了
/ext/standard/file.c1#define FPUTCSV_FLD_CHK(c) memchr(ZSTR_VAL(field_str), c, ZSTR_LEN(field_str))
其他看不太明白。
在非字符串类型搜索分支判断里面,还进行了详细的判断。总之: 要搜索的字符应该是字符串或者数值类型。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24static int php_needle_char(zval *needle, char *target)
{
switch (Z_TYPE_P(needle)) {
case IS_LONG:
*target = (char)Z_LVAL_P(needle);
return SUCCESS;
case IS_NULL:
case IS_FALSE:
*target = '