ID:技术让梦想更伟大
作者:李肖遥
在上篇因为一个函数strtok踩坑,我被老工程师无情嘲笑了(一),我们分析了strtok()函数,以及windos、Linux下的线程安全版,那么这篇中我们着重分析下解读strtok()的隐含特性,到底strtok有哪些坑。
看源码
要想深究其特性,必须看源码,下面的代码取自glibc-2.20的strtok.c文件。
1#include <string.h>
2
3static char *olds;
4
5#undef strtok
6
7#ifndef STRTOK
8# define STRTOK strtok
9#endif
10
11/* Parse S into tokens separated by characters in DELIM.
12 If S is NULL, the last string strtok() was called with is
13 used. For example:
14 char s[] = "-abc-=-def";
15 x = strtok(s, "-"); // x = "abc"
16 x = strtok(NULL, "-="); // x = "def"
17 x = strtok(NULL, "="); // x = NULL
18 // s = "abc\0=-def\0"
19*/
20char *
21STRTOK (char *s, const char *delim)
22{
23 char *token;
24
25 if (s == NULL)
26 s = olds;
27
28 /* Scan leading delimiters. */
29 s += strspn (s, delim);
30 if (*s == '\0')
31 {
32 olds = s;
33 return NULL;
34 }
35
36 /* Find the end of the token. */
37 token = s;
38 s = strpbrk (token, delim);
39 if (s == NULL)
40 /* This token finishes the string. */
41 olds = __rawmemchr (token, '\0');
42 else
43 {
44 /* Terminate the token and make OLDS point past it. */
45 *s = '\0'; /* 将分隔符所在位置置0,此为TOP2坑 */
46 olds = s + 1;
47 }
48 return token;
49}
但是百度百科里面又有提到说由速度更快的strsep()代替,所以又去查了下strsep函数:
#include <string.h>
#undef __strsep
#undef strsep
char *
__strsep (char **stringp, const char *delim)
{
char *begin, *end;
begin = *stringp;
if (begin == NULL)
return NULL;
/*A frequent case is when the delimiter string contains only one
character. Here we don't need to call the expensive `strpbrk'
function and instead work using `strchr`.*/
if (delim[0] == '\0' || delim[1] == '\0')
{
char ch = delim[0];
if (ch == '\0')
end = NULL;
else
{
if (*begin == ch)
end = begin;
else if (*begin == '\0')
end = NULL;
else
end = strchr (begin + 1, ch);
}
}
else
/* Find the end of the token.*/
end = strpbrk (begin, delim);
if (end)
{
/* Terminate the token and set *STRINGP past NUL character. */
*end++ = '\0';
*stringp = end;
}
else
/* No more delimiters; this is the last token. */
*stringp = NULL;
return begin;
}
踩坑指南1-不可重入
目前大部分程序都是在多线程环境下运行的,而这也是我们使用strtok容易犯错的原因之一。
我们在上一篇中看到 char buffer[INFO_MAX_SZ]="Aob male 18,Bob male 19,Cob female 20";
切割的时候仅仅提取出了第一个人的信息。
这就说明:strtok不可重入。为什么呢?
我们看上面的源码Line3,定义了一个全局变量
static char *olds;
使用了全局变量,也就是使用了静态缓冲区,因此该函数不可重入。
踩坑指南2-字符串首尾的分隔符会被忽略
strtok()函数是根据给定的字符集分隔字符串,并返回各子字符串。
我们看源码line28,跳过了字符串前面的分隔符,如果字符串只剩下尾部的分隔符,跳过前导符相当于忽略尾部的分隔符。
我们举个例子,来切割";Hello ;I'm lixiaoyao;;"
。
//https://tool.lu/coderunner/
//来源:技术让梦想更伟大
//作者:李肖遥
#include <stdio.h>
#include <string.h>
int main(void)
{
char szTest[] = ";Hello ;I'm lixiaoyao;;";
char *pSentence = NULL;
pSentence = strtok(szTest, ";");
while (NULL != pSentence)
{
printf("%s\n\n", pSentence);
pSentence = strtok(NULL, ";");
}
return 0;
}
编译结果如下,我们看到后面的“;”没有了。
踩坑指南3-连续的分隔符被当做一个分隔符处理
我们看源码line42,当找到一个分隔符就返回,下次进入该函数会跳过前导分隔符。
也就是说如果两个分隔符连续出现,那么在分隔的时候,你是希望分隔出一个空字符串,还是希望strtok忽略掉多余的分隔符呢?
我们来看一个例子说明就好了。代码如下:
//https://tool.lu/coderunner/
//来源:技术让梦想更伟大
//作者:李肖遥
#include <stdio.h>
#include <string.h>
int main(void)
{
char szTest[] = "Hello;;I'm lixiaoyao";//连续使用两个;分隔语句
char *pSentence = NULL;
pSentence = strtok(szTest, ";");
while (NULL != pSentence)
{
printf("%s\n\n", pSentence);
pSentence = strtok(NULL, ";");
}
return 0;
}
结果如下,连续出现的分隔符只被处理一次。
踩坑指南4-源字符串会被修改
如果一个字符串在我们的视线之外被修改了,那么可能会发生一些列诡异的事。实际上在源代码line45,将分隔符所在位置置0,也就是修改了源字符串,我们看如下代码:
//https://tool.lu/coderunner/
//来源:技术让梦想更伟大
//作者:李肖遥
#include <stdio.h>
#include <string.h>
int main(void)
{
char szTest[] = "Hello;I'm lixiaoyao";
char *pSentence = NULL;
printf("The original string is %s.\n", szTest);
pSentence = strtok(szTest, ";");
while (NULL != pSentence)
{
pSentence = strtok(NULL, ";");
}
printf("The final string is %s.\n", szTest);
return 0;
}
按照我们的理解,strtok只是切割了字符串,字符串本身不变化,实际上却变了,我们看结果 确实是变了。
避坑结论
尽量使用可重入版的strtok,Windows平台下为strtok_s函数,Linux平台下为strtok_r函数;
字符串分割其实还有很多方法,比如使用sscanf函数,在之后我会再学习分享;
牢记strtok函数族的分隔规则:忽略字符串前后的分隔符,连续的分隔符被当做一个处理;
在使用strtok前,请对源字符串进行备份,不然想找到错误可能比较会容易忽略。
最后
字符串分割还有很多知识,strtok和strtok_r的使用要点和实现原理还可以深究,从中我们也可以看到,找到问题的根源需要借助源码,尤其在嵌入式开发过程中,很多时候我们并不知道真正的原因是什么,但是当我们找到源码的时候,又是一目了然。
嵌入式编程专辑 Linux 学习专辑 C/C++编程专辑 关注微信公众号『技术让梦想更伟大』,后台回复“m”查看更多内容,回复“加群”加入技术交流群。 长按前往图中包含的公众号关注