因为一个函数strtok踩坑,我懂得了看源码的重要性
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 ”查看更多内容,回复“ 加群 ”加入技术交流群。
长按前往图中包含的公众号关注
本文分享自微信公众号 - 技术让梦想更伟大(gh_f7effb2fbc1c)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
10大高性能开发宝石,我要消灭一半程序员!
程序员经常要面临的一个问题就是:如何提高程序性能? 这篇文章,我们循序渐进,从内存、磁盘I/O、网络I/O、CPU、缓存、架构、算法等多层次递进,串联起高性能开发十大必须掌握的核心技术。 - I/O优化:零拷贝技术- I/O优化:多路复用技术-线程池技术-无锁编程技术-进程间通信技术-RPC&&序列化技术-数据库索引技术-缓存技术&&布隆过滤器-全文搜索技术-负载均衡技术 准备好了吗,坐稳了,发车! 首先,我们从最简单的模型开始。 老板告诉你,开发一个静态web服务器,把磁盘文件(网页、图片)通过网络发出去,怎么做? 你花了两天时间,撸了一个1.0版本: 主线程进入一个循环,等待连接 来一个连接就启动一个工作线程来处理 工作线程中,等待对方请求,然后从磁盘读文件、往套接口发送数据,完事儿 上线一天,老板发现太慢了,大一点的图片加载都有卡顿感。让你优化,这个时候,你需要: I/O优化:零拷贝技术 上面的工作线程,从磁盘读文件、再通过网络发送数据,数据从磁盘到网络,兜兜转转需要拷贝四次,其中CPU亲自搬运都需要两次。 零拷贝技术,解放CPU,文件数据直接从内...
- 下一篇
用 NetworkX + Gephi + Nebula Graph 分析人物关系(上篇)
我们都知道《权利的游戏》在全世界都很多忠实的粉丝,除去你永远不知道剧情下一秒谁会挂这种意外“惊喜”,当中复杂交错的人物关系也是它火爆的原因之一,而本文介绍如何通过 NetworkX 访问开源的分布式图数据库 Nebula Graph,并借助可视化工具—— Gephi 来可视化分析《权力的游戏》中的复杂的人物图谱关系。 数据集 本文的数据集来源:冰与火之歌第一卷(至第五卷)[1] 人物集 (点集):书中每个角色建模为一个点,点只有一个属性:姓名 关系集(边集):如果两个角色在书中发生过直接或间接的交互,则有一条边;边只有一个属性:权重,权重的大小代表交互的强弱。 这样的点集和边集构成一个图网络,这个网络存储在图数据库 Nebula Graph [2]中。 社区划分——Girvan-Newman 算法 我们使用 NetworkX [3] 内置的社区发现算法 Girvan-Newman 来为我们的图网络划分社区。 以下为「社区发现算法 Girvan-Newman」解释: 网络图中,连接较为紧密的部分可以被看成一个社区。每个社区内部节点之间有较为紧密的连接,而在两个社区间连接则较为稀疏。社区发...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS8编译安装MySQL8.0.19
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Windows10,CentOS7,CentOS8安装Nodejs环境