现在的位置: 首页 > 综合 > 正文

线程安全——strtok VS strtok_r

2013年08月22日 ⁄ 综合 ⁄ 共 10892字 ⁄ 字号 评论关闭

#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

抱歉!评论已关闭.