您现在的位置是:首页 > 文章详情

因为一个函数strtok踩坑,我懂得了看源码的重要性

日期:2020-08-19点击:400



关注、星标公众号 ,直达精彩内容

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
8define 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只是切割了字符串,字符串本身不变化,实际上却变了,我们看结果 确实是变了。

避坑结论

  1. 尽量使用可重入版的strtok,Windows平台下为strtok_s函数,Linux平台下为strtok_r函数;

  2. 字符串分割其实还有很多方法,比如使用sscanf函数,在之后我会再学习分享;

  3. 牢记strtok函数族的分隔规则:忽略字符串前后的分隔符,连续的分隔符被当做一个处理;

  4. 在使用strtok前,请对源字符串进行备份,不然想找到错误可能比较会容易忽略。

最后

字符串分割还有很多知识,strtok和strtok_r的使用要点和实现原理还可以深究,从中我们也可以看到,找到问题的根源需要借助源码,尤其在嵌入式开发过程中,很多时候我们并不知道真正的原因是什么,但是当我们找到源码的时候,又是一目了然。

推荐阅读:


 
 
嵌入式编程专辑
Linux 学习专辑
C/C++编程专辑

关注 微信公众号『技术让梦想更伟大』,后台回复“ m ”查看更多内容,回复“ 加群 ”加入技术交流群。

长按前往图中包含的公众号关注

本文分享自微信公众号 - 技术让梦想更伟大(gh_f7effb2fbc1c)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

原文链接:https://my.oschina.net/lixiaoyao614/blog/4512450
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章