php底层rtrim的一个“bug”
2017-10-19
背景
trim系列函数是用于去除字符串中首尾的空格或其他字符。ltrim函数只去除掉字符串首部的字符,rtrim函数只去除字符串尾部的字符。
string trim ( string $str [, string $character_mask = " \t\n\r\0\x0B" ] )
看一个例子:
$str = "e?type";
echo $str;
echo "\n";
echo rtrim($str, '?type');
echo "\n";
不知道别人怎么想,我觉得这个返回的应该是”e”吧。
但是实际上返回了一个空的字符串。
又是一个黑魔法吗?
源码
看一下php的底层实现:
这个是php7 trim系列函数的源码,红框内就是rtrim的代码。
这个是php_charmask的源码。
执行步骤
trim执行步骤
trim、ltrim、rtrim三个函数都是调用了php_do_trim函数,区别在于第二个参数mode的不同。本文主要对trim函数进行分析,ltrim和rtrim函数跟trim的类似。然后php_do_trim会调用了php_trim来实现功能,因此trim函数的核心函数时php_trim函数。其执行步骤如下:
- 根据what的值设置保存过滤字符的mask数组
- 过滤在字符串首部的待过滤字符
- 过滤在字符串尾部的待过滤字符
php_trim函数执行的流程图如下:
源码解读
php_trim函数先调用了php_charmask,这个函数试将过滤字符设置为mask[char] = 1的形式,这样就是一个哈希数组,然后可用于后面的判断。如果第二个参数是范围值时,调用了memset函数给mask数组赋值。
根据源码提炼出了以下的小demo:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void php_charmask(unsigned char *input, size_t len, char *mask);
char *rtrim(char *str,char *character_mask);
int main(int argc, char const *argv[])
{
printf("%s\n",rtrim("e?type","?type"));
return 0;
}
char *rtrim(char *str,char *character_mask)
{
char *res;
char mask[256];
register size_t i;
size_t len = strlen(str);
php_charmask((unsigned char*)character_mask, strlen(character_mask), mask);
if (len > 0) {
i = len - 1;
do {
if (mask[(unsigned char)str[i]]) {
len--;
} else {
break;
}
} while (i-- != 0);
}
res = (char *) malloc(sizeof(char) * (len+1));
memcpy(res,str,len);
return res;
}
void php_charmask(unsigned char *input, size_t len, char *mask)
{
unsigned char *end;
unsigned char c;
memset(mask, 0, 256);
for (end = input+len; input < end; input++) {
c = *input;
mask[c]= 1;
}
}
在php_charmask中,构造了一个简单的hash数组
在最上面的例子中,?type分别落到了这个数组中的不同位置(这也就是为什么mask的大小要设置成256,和ASCII的数量一致)。
所以在最上面的例子中,从后往前e,p,y,t,?,e都命中了hash。
结论
其实这也不算一个bug,文档说的很清楚,trim系列函数是字符级别的,可是惯性就让我们以为是字符串级别的,以后想清楚了再用。