#include<string.h>

char *strtok(char* restrict s1,const char* restrict s2);

strtok将字符串分隔成标记。对strtok的第一次调用与后继的调用不同。第一次调用的时候,将要解析的字符串地址作为第一个参数s1,传递进去。在后继的解析同一个字符串的调用中,用NULL作为第一个参数。
    对strtok的每个后继调用都返回下一个标记的起始,并在返回的标记末尾插入一个'/0'。当strtok函数到达s1的末尾时,就返回NULL。
    strtok没有为标记分配新的空间,而是就地对s1进行了标记,理解这一点是很重要的。因此,如果需要在调用该函数后访问原来的s1,就必须传递字符串的一个拷贝。
    这两个参数使用的限定符restrict要求:这个函数中s1引用的任何对象都不能被s2访问。也就是说,被解析的字符串的末端不能用来包含定界符。

但是,strtok不是一个线程安全的函数。因为根据其定义,它必须使用内部静态变量来记录字符串中下一个需要解析的标记的当前位置。但是,由于指示这个位置的变量只有一个,那么,在同一个程序中出现多个解析不同字符串的strtok调用时,各自的字符串的解析就会互相干扰。

下面这个程序是一个用来确定一个文本中的每行单词个数的平均次数的错误算法。wordaverage函数用来确定每一行,不幸的是,wordcount函数也使用了strtok,这一次是用它来解析本行中的字,这时,strtok保持的内部状态信息被改变了=,=

#include<string.h>
#define LINE_DELIMITERS "/n"
#define WORD_DELIMITERS " "

static int wordcount(char *s)
{
    int coutn=1;

if(strtok(s,WORD_DELIMITERS)==NULL)
        return 0;
    while(strtok(NULL,WORD_DELIMITERS)!=NULL)
        count++;
    return count;
}

double wordaverage(char *s)
{
    int linecount=1;
    char* nextline;
    int words;

nextline=strtok(s,LINE_DELIMITERS);
    if(nextline==NULL)
        return 0.0;
    words=wordcount(nextline);
    while((nextline=strtok(NULL,LINE_DELIMITERS))!=NULL){
        words+=wordcount(nextline);
        linecount++;
    }
    return (double)words/linecount;
}

POSIX定义了一个线程安全的函数——strtok_r,以此来代替strtok。_r表示可以重入(reentrant)。

(带有_r的函数主要来自于UNIX下面。 所有的带有_r和不带_r的函数的区别的是:带_r的函数是线程安全的,r的意思是reentrant,可重入的。)

#include<string.h>
    char * strtok_r(char* restrict s,const char* restrict sep,char **restrict lasts);

这样,对上面程序稍作修正,就能正确运行了。
#include<string.h>
#define LINE_DELIMITERS "/n"
#define WORD_DELIMITERS " "

static int wordcount(char *s)
{
    int coutn=1;
    char *lasts;

if(strtok_r(s,WORD_DELIMITERS,&lasts)==NULL)
        return 0;
    while(strtok_r(NULL,WORD_DELIMITERS,&lasts)!=NULL)
        count++;
    return count;
}

double wordaverage(char *s)
{
    int linecount=1;
    char* nextline;
    int words;
    char* lasts;

nextline=strtok_r(s,LINE_DELIMITERS,&lasts);
    if(nextline==NULL)
        return 0.0;
    words=wordcount(nextline);
    while((nextline=strtok_r(NULL,LINE_DELIMITERS,&lasts))!=NULL){
        words+=wordcount(nextline);
        linecount++;
    }
    return (double)words/linecount;
}

除了额外的参数lasts外,strtok_r函数与strtok的表现类似,lasts是用户提供的一个指针,指向strtok_r用来存放下一次解析的起始地址的那个单元。

文章出处:http://hi.baidu.com/pigfanfan/blog/item/72816c958d63e743d1135ebf.html

strtok()和strtok_r()

注:
下面的说明摘自于最新的Linux内核2.6.29,说明了strtok()这个函数已经不再使用,由速度更快的strsep()代替

/*
* linux/lib/string.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
*/
/*
* stupid library routines.. The optimized versions should generally be found
* as inline code in <asm-xx/string.h>
*
* These are buggy as well..
*
* * Fri Jun 25 1999, Ingo Oeser <ioe@informatik.tu-chemnitz.de>
* - Added strsep() which will replace strtok() soon (because strsep() is
* reentrant and should be faster). Use only strsep() in new code, please.
*
* * Sat Feb 09 2002, Jason Thomas <jason@topic.com.au>,
* Matthew Hawkins <matt@mh.dropbear.id.au>
* - Kissed strtok() goodbye
*/

strtok()这个函数大家都应该碰到过,但好像总有些问题, 这里着重讲下它

首先看下MSDN上的解释:

char *strtok( char *strToken, const char *strDelimit );

Parameters

strToken
String containing token or tokens.
strDelimit
Set of delimiter characters.

Return Value

Returns a pointer to the next token found in strToken. They return NULL when no more tokens are found. Each call modifies strToken by substituting a NULL character for each delimiter that is encountered.

Remarks

The strtok function finds the next token in strToken. The set of characters in strDelimit specifies possible delimiters of the token to be found in strToken on the current call.

Security Note     These functions incur a potential threat brought about by a buffer overrun problem. Buffer overrun problems are a frequent method of system attack, resulting in an unwarranted elevation of privilege. For more information, see Avoiding Buffer Overruns.

On the first call to strtok, the function skips leading delimiters and returns a pointer to the first token in strToken, terminating the token with a null character. More tokens can be broken out of the remainder of strToken by a series of calls to strtok. Each call to strtok modifies strToken by inserting a null character after the token returned by that call. To read the next token from strToken, call strtok with a NULL value for the strToken argument. The NULL strToken argument causes strtok to search for the next token in the modified strToken. The strDelimit argument can take any value from one call to the next so that the set of delimiters may vary.

Note    Each function uses a static variable for parsing the string into tokens. If multiple or simultaneous calls are made to the same function, a high potential for data corruption and inaccurate results exists. Therefore, do not attempt to call the same function simultaneously for different strings and be aware of calling one of these functions from within a loop where another routine may be called that uses the same function. However, calling this function simultaneously from multiple threads does not have undesirable effects.

很晕吧? 呵呵。。。

简单的说,就是函数返回第一个分隔符分隔的子串后,将第一参数设置为NULL,函数将返回剩下的子串。

下面我们来看一个例子:

int main() {

char test1[] = "feng,ke,wei";

char *test2 = "feng,ke,wei";

char *p; p = strtok(test1, ",");

while(p)

{

printf("%s/n", p);

p = strtok(NULL, ",");

}

return 0;

}

运行结果:

feng

ke

wei

但如果用p = strtok(test2, ",")则会出现内存错误,这是为什么呢?是不是跟它里面那个静态变量有关呢? 我们来看看它的原码:

/****strtok.c - tokenize a string with given delimiters**         Copyright (c) Microsoft Corporation. All rights reserved.**Purpose:*         defines strtok() - breaks string into series of token*         via repeated calls.********************************************************************************/
#include <cruntime.h>#include <string.h>#ifdef _MT#include <mtdll.h>#endif  /* _MT */
/****char *strtok(string, control) - tokenize string with delimiter in control**Purpose:*         strtok considers the string to consist of a sequence of zero or more*         text tokens separated by spans of one or more control chars. the first*         call, with string specified, returns a pointer to the first char of the*         first token, and will write a null char into string immediately*         following the returned token. subsequent calls with zero for the first*         argument (string) will work thru the string until no tokens remain. the*         control string may be different from call to call. when no tokens remain*         in string a NULL pointer is returned. remember the control chars with a*         bit map, one bit per ascii char. the null char is always a control char.*       //这里已经说得很详细了!!比MSDN都好! *Entry:*         char *string - string to tokenize, or NULL to get next token*         char *control - string of characters to use as delimiters**Exit:*         returns pointer to first token in string, or if string*         was NULL, to next token*         returns NULL when no more tokens remain.**Uses:**Exceptions:********************************************************************************/
char * __cdecl strtok (          char * string,          const char * control          ){          unsigned char *str;          const unsigned char *ctrl = control;
          unsigned char map[32];          int count;
#ifdef _MT          _ptiddata ptd = _getptd();#else  /* _MT */          static char *nextoken;                          //保存剩余子串的静态变量      #endif  /* _MT */
          /* Clear control map */          for (count = 0; count < 32; count++)                  map[count] = 0;
          /* Set bits in delimiter table */          do {                  map[*ctrl >> 3] |= (1 << (*ctrl & 7));          } while (*ctrl++);
          /* Initialize str. If string is NULL, set str to the saved           * pointer (i.e., continue breaking tokens out of the string           * from the last strtok call) */          if (string)                  str = string;                               //第一次调用函数所用到的原串          
else#ifdef _MT                  str = ptd->_token;#else  /* _MT */                str = nextoken;                        //将函数第一参数设置为NULL时调用的余串
#endif  /* _MT */
          /* Find beginning of token (skip over leading delimiters). Note that           * there is no token iff this loop sets str to point to the terminal           * null (*str == '/0') */          while ( (map[*str >> 3] & (1 << (*str & 7))) && *str )                  str++;
        string = str;                                    //此时的string返回余串的执行结果 
          /* Find the end of the token. If it is not the end of the string,           * put a null there. */
//这里就是处理的核心了, 找到分隔符,并将其设置为'/0',当然'/0'也将保存在返回的串中          for ( ; *str ; str++ )                  if ( map[*str >> 3] & (1 << (*str & 7)) ) {                        *str++ = '/0';                //这里就相当于修改了串的内容 ①                          break;                  }
          /* Update nextoken (or the corresponding field in the per-thread data           * structure */#ifdef _MT          ptd->_token = str;#else  /* _MT */        nextoken = str;                   //将余串保存在静态变量中,以便下次调用#endif  /* _MT */
          /* Determine if a token has been found. */          if ( string == str )                return NULL;          else                  return string;

1. strtok介绍

众所周知,strtok可以根据用户所提供的分割符(同时分隔符也可以为复数比如“,。”)

将一段字符串分割直到遇到"/0".

比如,分隔符=“,” 字符串=“Fred,John,Ann”

通过strtok 就可以把3个字符串 “Fred”      “John”       “Ann”提取出来。

上面的C代码为
QUOTE:
int in=0;
char buffer[]="Fred,John,Ann"
char *p[3];
char *buff = buffer;
while((p[in]=strtok(buf,","))!=NULL) {
i++;
buf=NULL; }

如上代码,第一次执行strtok需要以目标字符串的地址为第一参数(buf=buffer),之后strtok需要以NULL为第一参数 (buf=NULL)。指针列p[],则储存了分割后的结果,p[0]="John",p[1]="John",p[2]="Ann",而buf就变 成    Fred/0John/0Ann/0。

2. strtok的弱点
让我们更改一下我们的计划:我们有一段字符串 "Fred male 25,John male 62,Anna female 16" 我们希望把这个字符串整理输入到一个struct,

QUOTE:
struct person {
char [25] name ;
char [6] sex;
char [4] age;
}

要做到这个,其中一个方法就是先提取一段被“,”分割的字符串,然后再将其以“ ”(空格)分割。
比如: 截取 "Fred male 25" 然后分割成 "Fred" "male" "25"
以下我写了个小程序去表现这个过程:

QUOTE:
#include<stdio.h>
#include<string.h>
#define INFO_MAX_SZ 255
int main()
{
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;
}
p[in++]="***"; //表现分割
buf=NULL; }

printf("Here we have %d strings/n",i);
for (int j=0; j<in; j++)
printf(">%s</n",p[j]);
return 0;
}

这个程序输出为:
Here we have 4 strings
>Fred<
>male<
>25<
>***<
这只是一小段的数据,并不是我们需要的。但这是为什么呢? 这是因为strtok使用一个static(静态)指针来操作数据,让我来分析一下以上代码的运行过程:

红色为strtok的内置指针指向的位置,蓝色为strtok对字符串的修改

1. "Fred male 25,John male 62,Anna female 16" //外循环

2. "Fred male 25/0John male 62,Anna female 16" //进入内循环

3. "Fred/0male 25/0John male 62,Anna female 16"

4. "Fred/0male/025/0John male 62,Anna female 16"

5. "Fred/0male/025/0John male 62,Anna female 16" //内循环遇到"/0"回到外循环

6. "Fred/0male/025/0John male 62,Anna female 16" //外循环遇到"/0"运行结束。

3. 使用strtok_r
在这种情况我们应该使用strtok_r, strtok reentrant.
char *strtok_r(char *s, const char *delim, char **ptrptr);

相对strtok我们需要为strtok提供一个指针来操作,而不是像strtok使用配套的指针。
代码:

QUOTE:
#include<stdio.h>
#include<string.h>
#define INFO_MAX_SZ 255
int main()
{
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;
}
p[in++]="***";
buf=NULL; }

printf("Here we have %d strings/n",i);
for (int j=0; jn<i; j++)
printf(">%s</n",p[j]);
return 0;
}

这一次的输出为:
Here we have 12 strings
>Fred<
>male<
>25<
>***<
>John<
>male<
>62<
>***<
>Anna<
>female<
>16<
>***<

让我来分析一下以上代码的运行过程:

红色为strtok_r的outer_ptr指向的位置,
紫色为strtok_r的inner_ptr指向的位置,
蓝色为strtok对字符串的修改

1.  "Fred male 25,John male 62,Anna female 16" //外循环

2.  "Fred male 25/0John male 62,Anna female 16"//进入内循环

3.  "Fred/0male 25/0John male 62,Anna female 16"

4.  "Fred/0male/025/0John male 62,Anna female 16"

5.  "Fred/0male/025/0John male 62,Anna female 16" //内循环遇到"/0"回到外循环

6.  "Fred/0male/025/0John male 62/0Anna female 16"//进入内循环
}

原来, 该函数修改了原串.

所以,当使用char *test2 = "feng,ke,wei"作为第一个参数传入时,在位置①处, 由于test2指向的内容保存在文字常量区,该区的内容是不能修改的,所以会出现内存错误. 而char test1[] = "feng,ke,wei" 中的test1指向的内容是保存在栈区的,所以可以修改.

看到这里 大家应该会对文字常量区有个更加理性的认识吧.....

文章出处:http://hi.baidu.com/shallfun/blog/item/b9abe608dd14012e6a60fb5c.html

线程安全——strtok VS strtok_r相关推荐

  1. strtok和strtok_r

    strtok和strtok_r 原型:char *strtok(char *s, char *delim); 功能:分解字符串为一组字符串.s为要分解的字符串,delim为分隔符字符串. 说明:首次调 ...

  2. 关于函数strtok和strtok_r的使用要点和实现原理(二)【转】

    本文转载自:http://astute11.blog.51cto.com/4404646/1334199 (一)中已经介绍了使用strtok函数的一些注意事项,本篇将介绍strtok的一个应用并引出s ...

  3. mysql strtok_c函数: strtok 和 strtok_r 详解

    函数名:   strtok 功     能:   查找由在第二个串中指定的分界符分隔开的单词 用     法:   char   *strtok(char   *str1,   char   *str ...

  4. C语言字符串截取函数strtok和strtok_r

    在看源码的时候需要将一段并排的IPs转化成为一系列的IP,将"10.0.0.1;10.0.0.2;10.0.0.3;10.0.0.4;10.0.0.5"转换成为单独的"1 ...

  5. strtok及strtok_r的应用!

    函数定义及头文件:char *strtok(char *s,  const char *delim)   头文件:<string.h>.该函数的作用是分割字符串,参数s执行欲分割的字符串, ...

  6. mysql strtok,strtok()和strtok_r()

    下面的说明摘自于最新的Linux内核2.6.29,说明了strtok()这个函数已经不再使用,由速度更快的strsep()代替 /* * linux/lib/string.c * * Copyrigh ...

  7. strtok和strtok_r最通俗易懂的理解

    在网上看了一圈,全是复制粘贴的官方解释,还不如自己写几行代码理解得快,真的是百看不如一试 strtok用法 char *token = strtok(char *str, char *delim): ...

  8. 关于函数strtok和strtok_r的使用要点和实现原理(一)

    strtok函数的使用是一个老生常谈的问题了.该函数的作用很大,争议也很大.以下的表述可能与一些资料有区别或者说与你原来的认识有差异,因此,我尽量以实验为证.交代一下实验环境是必要的,winxp+vc ...

  9. strtok、strtok_r 、strsep函数的问题

    首先看第一个strtok: 虽然strtok有诸多问题,已经被Linux kernel淘汰,由strsep替代,但了解这个函数的实现对我们理解C语言的运用极有裨益,也有过知名企业的面试中甚至出现了st ...

最新文章

  1. input输入框为number类型时,去掉上下小箭头
  2. 《C++入门经典(第5版•修订版)》——2.6 问与答
  3. 022_html计算机输出标签
  4. 【项目管理】专用中英文术语词汇 205
  5. SYN 攻击原理以及防范技术
  6. php迭代器作用,PHP迭代器介绍
  7. 循序渐进PYTHON3(十三) --4-- DJANGO之CSRF使用
  8. 华师计算机基础在线作业秋,18秋华师《计算机基础》在线作业.docx
  9. 极棒开启AI挑战 全球寻找顶级语音合成“机械师”
  10. Wordle是优秀的信息可视化吗?如何真正使用Wordle?
  11. for循环中的setTimeout()
  12. FCM模糊聚类算法python实现
  13. java jdom_Java JDOM解析器
  14. 用三元组存储稀疏矩阵,实现其快速转置及矩阵相乘
  15. android pc游戏模拟器哪个好用,哪个电脑手游模拟器好用 安卓手游模拟器测试对比排行榜...
  16. python调用不起来chrome_python调用selenium打开chrome浏览器失败
  17. ThinkPHPdayo01学习笔记(体系化,系统化笔记)
  18. 就聊聊不少小IT公司的技术总监
  19. [笔记分享] [Tools] QPST使用过程
  20. 【ps合成】给男票做个Q版大头像

热门文章

  1. 差序格局与关系取向社会
  2. 用Ogre实现画中画 [ 截图 ]
  3. win10耳机有杂音滋滋_蓝牙耳机的底噪和电流声有区别吗?双11五款高续航平价蓝牙耳机分享...
  4. HTML5 PDF 编辑,pdf.js的使用与改造
  5. BPSK码元速率与带宽的关系
  6. quill光标位置插入html,quill编辑器+word文档上传,插入指定位置
  7. 【C++】setw()函数
  8. librtmp h265 推流
  9. c#中如何进行com口操作?
  10. 【力扣周赛#324】6266. 使用质因数之和替换后可以取到的最小值+6267. 添加边使所有节点度数都为偶数+6268. 查询树中环的长度