背景
C语言的字符串库:<string.h>
臭名昭著:它毫无作为字符串的自我修养。使用原生C语言解析文本是一件相当腌臜 的事情:在清晰的逻辑以外,还需要良好的耐心来hard code在使用python等语言时视作理所应当的低级字符串处理函数。更糟糕的是,在我这样的C初学者看来,GNU manual讲的不清不楚。这篇笔记总结通过阅读源码获得的<string.h>
函数用法。
char* strtok(char* s, const char* delim)
—— Python一个split函数搞定的事情,你怎么搞得这么复杂?
——分配内存太麻烦了,我帮你标好 \0
了,你从原始字符串上自取子字符串吧。
使用
不同于strdup
这样会自动分配堆内存的函数,strtok
不为子字符串分配额外内存。它改动传入的字符串:把delimiter的字节改成\0
。因此有这样几个注意点:
- 在使用这个函数之后,传入的字符串就废掉啦!传进去之前做好备份。
- 不可以对静态字符串使用该函数。否则SEGFAULT。
char str[] = "a=1";
char* delim = "=";
char* token = strtok(str, delim);
while (token != NULL) {
puts(token);
token = strtok(NULL, &delim);
}
源码分析 [link]
//strtok.c
char* strtok(char* s, const char* delim) {
static char* olds;
return __strtok_r(s, delim, &olds);
}
//strtok_r.c
char *__strtok_r (char *s, const char *delim, char **save_ptr) {
char* end;
if (s == NULL)
s = *save_ptr;
if (*s == '\0') {
*save_ptr = s;
return NULL;
}
/*Scan leading delimeters*/
s += strspn(s, delim);
if (*s == '\0') {
*save_ptr = s;
return NULL;
}
/*Find the end of the token*/
end = s + strcspn(s, delim);
if (*s == '\0') {
*save_ptr = s;
return s;
}
/*Terminate the token and make *SAVE_PTR point past it*/
*end = '\0';
*save_ptr = end+1;
return s;
}
size_t strspn(char* s, const char* accept)
返回输入字符串中首个完全由accept
中字符组成的子字符串长度。
// strspn.c
size_t STRSPN(const char *str, const char* accept)
{
if (accept[0] = '\0') // super edge case hahaha
return 0;
if (__glibc_unlikely(accept[1]='\0') // 分支预测,辅助编译器生成更高效的代码
{
const char *a = str;
for (; *str==*a; str++);
return str-a;
}
/*use multiple small memsets to enable inlining on most targets ?????*/
unsigned char table[256]; // 自建字符表格!
unsighed char* p = memset(table, 0, 64);
memset(p+64, 0, 64);
memset(p+128, 0, 64);
memset(p+192, 0, 64);
unsigned char *s = (unsigned char*) accept;
/* Different from strcspn it does not add the NULL on the table
so can avoid check if str[i] is NULL, since table['\0'] will
be 0 and thus stopping the loop check. */
do
p[*s++] = 1;
while (*s);
s = (unsigned char*)str;
if(!p[s[0]) return 0;
if(!p[s[1]) return 1;
if(!p[s[2]) return 2;
if(!p[s[3]) return 3;
s = (unsigned char*) PTR_ALIGN_DOWN(s, 4); //指针对齐
}
- 分支预测
- 疑惑:inlining on most targets 是啥??
- 疑惑:这里强制指针对齐的目的?
size_t strcspn(char* s, const char* reject)
返回输入字符串中首个不含reject
中字符的子字符串长度。
源码分析 [link]
size_t STRSCPN(const char* str, const char* reject)
{
if (__glibc_unlikely(reject[0] == '\0') || __glibc_unlikely(reject[1] == '\0'))
return __strchrnul(str, reject[0]) - str;
/* Use multiple small memsets to enable inlining on most targets. */
unsigned char table[256];
unsigned char *p = memset (table, 0, 64);
memset (p + 64, 0, 64);
memset (p + 128, 0, 64);
memset (p + 192, 0, 64);
unsigned char *s = (unsigned char*) reject;
unsigned char tmp;
do
p[tmp = *s++] = 1;
while (tmp);
s = (unsigned char*) str;
if (p[s[0]]) return 0;
if (p[s[1]]) return 1;
if (p[s[2]]) return 2;
if (p[s[3]]) return 3;
s = (unsigned char *) PTR_ALIGN_DOWN (s, 4);
unsigned int c0, c1, c2, c3;
do
{
s += 4;
c0 = p[s[0]];
c1 = p[s[1]];
c2 = p[s[2]];
c3 = p[s[3]];
}
while ((c0 | c1 | c2 | c3) == 0);
size_t count = s - (unsigned char *) str;
return (c0 | c1) != 0 ? count - c0 + 1 : count - c2 + 3;
}
- pro: 以4为一个batch批量处理字符。