关于函数strtok和strtok_r的使用要点和实现原理

请给我倒杯茶 请给我倒杯茶     2022-08-07     364

关键词:

本文转载自:http://astute11.blog.51cto.com/4404646/1334199

(一)中已经介绍了使用strtok函数的一些注意事项,本篇将介绍strtok的一个应用并引出strtok_r函数。

 

1.一个应用实例

网络上一个比较经典的例子是将字符串切分,存入结构体中。如,现有结构体

1
2
3
4
5
typedef struct person{
    char name[25];
    char sex[10];
    char age[4];
}Person;

需从字符串 char buffer[INFO_MAX_SZ]="Fred male 25,John male 62,Anna female 16"; 中提取出人名、性别以及年龄。

一种可行的思路是设置两层循环。外循环,先以 ‘,’ (逗号) 为分界符,将三个人的信息分开,然后对于每一个子串,再以 ‘ ’(空格) 为分界符分别得到人名、性别和年龄。

按照这个思路,理应能够实现所要的功能。为了简化步骤,我们调用strtok,先将子串先一一保存到字符串指针数组中,程序末尾打印指针数组中保存的所有子串,验证程序的正确性。得到的程序应该如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int in=0;
char buffer[INFO_MAX_SZ]="Fred male 25,John male 62,Anna female 16";  
char *p[20];
char *buf = buffer;
while((p[in]=strtok(buf,","))!=NULL)
{
    buf=p[in];
    while((p[in]=strtok(buf," "))!=NULL)
    {
        in++;
        buf=NULL;
    }
    buf=NULL;
}
printf("Here we have %d strings/n"in);
for (int j=0; j<in; j++)
    printf(">%s</n",p[j]);
}

技术分享

 

执行的结果是,仅仅提取出了第一个人的信息。看来程序的执行并没有按照我们的预想。原因是什么?

原因是:在第一次外循环中,strtok将"Fred male 25,"后的这个逗号,改为了‘’,这时strtok内部的this指针指向的是逗号的后一个字符‘J’经过第一次的内循环,分别提取出了“Fred” “male” “25”。提取完"25”之后,函数内部的this指针被修改指向了"25”后面的‘’内循环结束后(内循环实际执行了4次),开始第二次的外循环,由于函数第一个参数被设定为NULL,strtok将以this指针指向的位置作为分解起始位置。很遗憾,此时this指针指向的是‘’,strtok对一个空串无法切分,返回NULL。外循环结束。所以,我们只得到了如图所示的第一个人的信息。

 

看来使用strtok并不能通过两层循环的办法,解决提取多人信息的问题。有没有其他办法呢? 显然,是有其他途径的。

我给出了一种解决办法。同时以 ‘,’ (逗号) 和 ‘ ’(空格) 为分界符,一层循环解决问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
in 0;
while ((p[in] = strtok(buf, " ,")) != NULL)
{
    switch (in 3)
    {
    case 0:
        printf("第%d个人:Name!/n"in/3+1);
        break;
    case 1:
        printf("第%d个人:Sex!/n"in/3+1);
        break;
    case 2:
        printf("第%d个人:Age!/n"in/3+1);
        break;
    }
    in++;
    buf = NULL;
}
printf("Here we have %d strings/n"in);
for (int j=0; j<in; j++)
    printf(">%s</n",p[j]);
}

技术分享

 

程序虽然可以达到理想的结果,但不是一个太好解决方案。程序要求你在提取之前必须要知道一个结构体中究竟包含了几个数据成员。明显不如双重循环那样直观。

倘若一定要采用二重循环那种结构提取,有没有合适的函数能够代替strtok呢? 有的,它就是strtok_r。

 

2.strtok_r及其使用

strtok_r是linux平台下的strtok函数的线程安全版。windows的string.h中并不包含它。要想使用这个函数,上网搜其linux下的实现源码,复制到你的程序中即可。别的方式应该也有,比如使用GNU C Library。我下载了GNU C Library,在其源代码中找到了strtok_r的实现代码,复制过来。可以看作是第一种方法和第二种方法的结合。

strtok的函数原型为 char *strtok_r(char *str, const char *delim, char **saveptr);

下面对strtok的英文说明摘自http://www.linuxhowtos.org/manpages/3/strtok_r.htm,译文是由我给出的。

The strtok_r() function is a reentrant version strtok(). Thesaveptr argument is a pointer to a char *variable that is used internally bystrtok_r() in order to maintain context between successive calls that parse the same string.

strtok_r函数是strtok函数的可重入版本。char **saveptr参数是一个指向char *的指针变量,用来在strtok_r内部保存切分时的上下文,以应对连续调用分解相同源字符串。

On the first call to strtok_r(), str should point to the string to be parsed, and the value ofsaveptris ignored. In subsequent calls, str should be NULL, andsaveptr should be unchanged since the previous call.

第一次调用strtok_r时,str参数必须指向待提取的字符串,saveptr参数的值可以忽略。连续调用时,str赋值为NULL,saveptr为上次调用后返回的值,不要修改。

Different strings may be parsed concurrently using sequences of calls to strtok_r() that specify different saveptr arguments.

一系列不同的字符串可能会同时连续调用strtok_r进行提取,要为不同的调用传递不同的saveptr参数。

The strtok() function uses a static buffer while parsing, so it‘s not thread safe. Usestrtok_r() if this matters to you.

strtok函数在提取字符串时使用了静态缓冲区,因此,它是线程不安全的。如果要顾及到线程的安全性,应该使用strtok_r。

 

strtok_r实际上就是将strtok内部隐式保存的this指针,以参数的形式与函数外部进行交互。由调用者进行传递、保存甚至是修改。需要调用者在连续切分相同源字符串时,除了将str参数赋值为NULL,还要传递上次切分时保存下的saveptr。

举个例子,还记得前文提到的提取结构体的例子么?我们可以使用strtok_r,以双重循环的形式提取出每个人的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int in=0;
char buffer[INFO_MAX_SZ]="Fred male 25,John male 62,Anna female 16";
char *p[20];
char *buf=buffer;
char *outer_ptr=NULL;
char *inner_ptr=NULL;
while((p[in] = strtok_r(buf, ",", &outer_ptr))!=NULL)
{
    buf=p[in];
    while((p[in]=strtok_r(buf, " ", &inner_ptr))!=NULL)
    {
        in++;
        buf=NULL;
    }
    buf=NULL;
}
printf("Here we have %d strings/n",in);
for (int j=0; j<in; j++)
    printf(">%s</n",p[j]);
}

技术分享

调用strtok_r的代码比调用strtok的代码多了两个指针,outer_ptr和inner_ptr。outer_ptr用于标记每个人的提取位置,即外循环;inner_ptr用于标记每个人内部每项信息的提取位置,即内循环。具体过程如下:

(1)第1次外循环,outer_ptr忽略,对整个源串提取,提取出"Fred male 25",分隔符‘,‘ 被修改为了‘’,outer_ptr返回指向‘J’。

(2)第一次内循环,inner_ptr忽略对第1次外循环的提取结果"Fred male 25"进行提取,提取出了"Fred",分隔符‘ ‘被修改为了‘‘,inner_ptr返回指向‘m‘。

(3)第二次内循环,传递第一次内循环返回的inner_ptr,第一个参数为NULL,从inner_ptr指向的位置‘m‘开始提取,提取出了"male",分隔符  ‘ ‘被修改为了‘‘,inner_ptr返回指向‘2‘。

(4)第三次内循环,传递第二次内循环返回的inner_ptr,第一个参数为NULL,从inner_ptr指向的位置‘2‘开始提取,提取出了"25",因为没有找到‘ ‘,inner_ptr返回指向25后的‘‘。

(5)第四次内循环,传递第三次内循环返回的inner_ptr,第一个参数为NULL,因为inner_ptr指向的位置为‘‘,无法提取,返回空值。结束内循环。

(6)第2次外循环,传递第1次外循环返回的outer_ptr,第一个参数为NULL,从outer_ptr指向的位置‘J‘开始提取,提取出"John male 62",分隔符‘,’被修改为了‘’,outer_ptr返回指向‘A’。(调用strtok则卡死在了这一步

……以此类推,外循环一次提取一个人的全部信息,内循环从外循环的提取结果中,二次提取个人单项信息。

可以看到strtok_r将原内部指针显示化,提供了saveptr这个参数。增加了函数的灵活性和安全性。

 

3.strtok和strtok_r的源代码

这两个函数的实现,有众多的版本。我strtok_r来自于GNU C Library,strtok则调用了strtok_r。因此先给出strtok_r的源代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* Parse S into tokens separated by characters in DELIM.
   If S is NULL, the saved pointer in SAVE_PTR is used as
   the next starting point.  For example:
        char s[] = "-abc-=-def";
        char *sp;
        x = strtok_r(s, "-", &sp);      // x = "abc", sp = "=-def"
        x = strtok_r(NULL, "-=", &sp);  // x = "def", sp = NULL
        x = strtok_r(NULL, "=", &sp);   // x = NULL
                // s = "abc-def"
*/
char *strtok_r(char *s, const char *delim, char **save_ptr) {
    char *token;
    if (s == NULL) s = *save_ptr;
    /* Scan leading delimiters.  */
    s += strspn(s, delim);
    if (*s == ‘‘)
        return NULL;
    /* Find the end of the token.  */
    token = s;
    s = strpbrk(token, delim);
    if (s == NULL)
        /* This token finishes the string.  */
        *save_ptr = strchr(token, ‘‘);
    else {
        /* Terminate the token and make *SAVE_PTR point past it.  */
        *s = ‘‘;
        *save_ptr = s + 1;
    }
    return token;
}

 

代码整体的流程如下:

(1)判断参数s是否为NULL,如果是NULL就以传递进来的save_ptr作为起始分解位置;若不是NULL,则以s开始切分。

(2)跳过待分解字符串开始的所有分界符。

(3)判断当前待分解的位置是否为‘‘,若是则返回NULL(联系到(一)中所说对返回值为NULL的解释);不是则继续。

(4)保存当前的待分解串的指针token,调用strpbrk在token中找分界符:如果找不到,则将save_ptr赋值为待分解串尾部‘‘所在的位置,token没有发生变化;若找的到则将分界符所在位置赋值为‘‘,token相当于被截断了(提取出来),save_ptr指向分界符的下一位。

(5)函数的最后(无论找到还是没找到)都将返回。

对于函数strtok来说,可以理解为用一个内部的静态变量将strtok_r中的save_ptr给保存起来,对调用者不可见。其代码如下:

1
2
3
4
5
char *strtok(char *s, const char *delim)
{
    static char *last;
    return strtok_r(s, delim, &last);
}

 

有了上述两个函数的实现代码,再理解(一)(二)中所讲的一些要点也就不困难了。

花那么多篇幅总结这两个函数,一来是因为很多人对于strtok的误解比较深,网上很少有对于其非常详细的讨论,因此总结一份比较全面的材料,是有必要的;二来这也是自己不断学习的一个过程,总结会得到远比两个函数重要很多的信息。

字符串分割函数strtok(线程不安全),线程安全函数strtok_r

strtok_r函数---字符串分割函数函数原型:    char*strtok_r(char*str,constchar*delim,char**saveptr);参数:str:被分割的字符串,若str为NULL,则被分割的字符串为*saveptrdelim:依据此字符串分割strsaveptr:分割后剩余部分的字符串... 查看详情

strtok_r 导致“赋值使指针从整数不进行强制转换”

】strtok_r导致“赋值使指针从整数不进行强制转换”【英文标题】:strtok_rcausing"assignmentmakespointerfromintegerwithoutacast"【发布时间】:2013-06-0514:41:24【问题描述】:我正在尝试在C中标记字符串并使用strtok_r将标记保存到多个... 查看详情

Strtok_r 返回 NULL

】Strtok_r返回NULL【英文标题】:Strtok_rreturningNULL【发布时间】:2019-06-1914:47:47【问题描述】:我正在尝试对从文件中提取的字符串进行标记。strtok_r在第一个子字符串上正常工作,然后返回null(以及分段错误,因为我尝试将strndu... 查看详情

c语言源码剖析与实现——strtok()系列函数实现(代码片段)

...外几个函数strspn()、strpbrk()、strcspn()strspn()strcspn()strpbrk()strtok_r()源代码实现(不依赖其他函数库)完全自己实现strtok()设计方案代码实现性能分析测试用例源码剖析与实践strtok()源代码实现由于是用的static实现的全局变量存储地址... 查看详情

使用strtok从字符串中解析空标记(代码片段)

...nt与多线程无关.strtok已经使用嵌套循环中断了。可以使用strtok_r,但它不是那么便携。)另一答案这是strtok的限制。设计师考虑到了以空格分隔的标记。strtok无论如何都做不了多少;只需滚动你自己的解析器。CFAQhasanexample。另一... 查看详情

函数内部还是不要使用strtok()

...的时候,突然想起可能是 strtok()引起的,查找调用的函数,果然发现在函数中使用了 strtok()。而现在的问题就是在另一段代码中先使用了 strtok(),然后在没有结束前,又调用了一个内部使用 strtok()的函数,导致了&nb... 查看详情

c语言最短时间带你实现strtok,字符串分割函数,建议收藏!!!(代码片段)

...结↗️↗️↗️建议三连,以防丢失前言字符串分割函数strtok,大家可能都知道他怎么使用,一旦要用的时候就会心生疑惑,不知道它的内部的实现,废话不多说,本篇就来带大家看看strtok的基本使用和实... 查看详情

strtok函数的使用注意事项

1.函数原型及其基本应用   strtok函数是用来分解字符串的,其原型是: [cpp] viewplain copy char *strtok(char str[], const char *delim);    其中str是要分解的字符 查看详情

关于function和task的说明

 1. 关于函数function调用,总结两个要点:      1. 函数调用一般产生一个值,这个值被赋值给某个变量      2. 函数所返回的值只能是一个,不可以是多个,不能像C语言中... 查看详情

c基础函数的使用(代码片段)

目录一、库函数的使用1.1随机数rand与srand1.2scanf函数1.3gets函数1.4fgets函数1.5puts函数1.6strlen函数1.7strcat函数和strncat函数1.8strcmp和strncmp1.9strcpy和strncpy1.10sprintf函数1.11sscanf函数1.12strchr和strstr函数1.13strtok函数1.14atoi和atof, 查看详情

如何将 atoi 和 strtok 与多维数组一起使用?

】如何将atoi和strtok与多维数组一起使用?【英文标题】:Howtoconvertusingatoiandstrtokwithmultidimesionalarrays?【发布时间】:2021-03-0819:55:55【问题描述】:Okaysoihavethismltidimarrayfilledwiththesenumbers,我希望在这些位置将它们转换为整数所以它... 查看详情

关于androidstudio调用高德地图的简单流程和要点

一,账号与Key的申请注册成为高德开发者需要分三步:第一步,注册高德开发者;第二步,去控制台创建应用;第三步,获取Key。前2步都比较简单,这里说下第三步。获取Key1、进入控制台,创建一个新应用。如果您之前已经创... 查看详情

关于mapstate和mapmutations和mapgetters和mapactions辅助函数的用法及作用-----mapstate

一、通过mapState函数的对象参数来赋值:<p>{{count}}</p><p>{{count1}}</p><p>{{count2}}<p>//导入import{mapState}from‘vuex‘exportdefault{data(){return{msg:‘vuex理解要点‘,id:1}},store,//方法二: 查看详情

strtok() 可以安全使用吗?

...布时间】:2015-06-0109:50:45【问题描述】:我正在阅读很多关于strtok()的负面信息,有人说它已经过时,有人说它不是线程安全的,等等。那么真相是什么,我可以用strtok()吗?它是线程安全的吗?注意:我使用的是VisualC++。【问... 查看详情

个税小游戏要点之节流函数(提高动画流畅性)(代码片段)

...用频率的控制器,这里只做简单的介绍,如果想了解更多关于这两个定义的细节可以看下后文给出的一张图片,或者阅读一下lodash的文档。throttle:将一个函数的调用频率限制在一定阈值内,例如1s内一个函数不能被调用两次。de... 查看详情

为啥当我使用不同版本的 GCC 时使用 strtok 函数时出现此错误?

】为啥当我使用不同版本的GCC时使用strtok函数时出现此错误?【英文标题】:WhyisthiserroronusingstrtokfunctionshowingwhenIuseadifferentversionofGCC?为什么当我使用不同版本的GCC时使用strtok函数时出现此错误?【发布时间】:2020-08-2823:56:54【... 查看详情

strtok()出现segmentfault的错误(代码片段)

...通过空格分割成一个个字符串参数,这里我使用了strtok()函数,然后遇到了segmentfault的错误。出现问题的代码如下:终于寻找到原因:strtok(char*string,char*delim)函数的实现逻辑是函数是在s中查找包含在delim中的字符并用NULL(’/0′)... 查看详情

strtok的使用(代码片段)

/*strtok函数的使用*/#include<stdio.h>#include<stdlib.h>#include<string.h>//函数原型://char*strtok(char*str,constchar*delim)//参数://str--要被分解成一组小字符串的字符串//delim--包含分隔符的C字符串//返回值//该函数返回被分解的第一... 查看详情