多线程安全strtok函数MStrTok

    在做字符串分析的时候,常常会用到字符串分割技术,一般都会想到使用strtok,但遗憾的是,strtok函数是在多线程概念尚未普及的时候写的,没有考虑多线程会带来使用该函数会带来意外危险的问题。

 

    下面简单分析一下strtok在多线程程序里面使用的可能的危险。

     strtok的内部实现上,使用了一个静态地址指针,指向上一次分析完成以后的字符串的结束的地址。

  1. UINT  Thread1(LPVOID pParam)
  2. {
  3.          char szSrc[128] = "THIS,IS,A,TEST";
  4.          char szSeps[32] = ",";
  5.          char *pToken = strtok(szSrc, szSeps);
  6.         while(pToken != NULL)
  7.         {
  8.             cout << pToken << endl;
  9.             pToken = strtok(NULL, szSeps);
  10.         }         
  11. }
  12.     
  13. UINT  Thread2(LPVOID pParam)
  14. {
  15.          char szSrc[128] = "HELLO WORLD THIS IS STRTOK TEST";
  16.          char szSeps[32] = " ";
  17.          char *pToken = strtok(szSrc, szSeps);
  18.         while(pToken != NULL)
  19.         {
  20.             cout << pToken << endl;
  21.             pToken = strtok(NULL, szSeps);
  22.         }         
  23. }

     当程序执行的时候,可能Thread1先执行  char *pToken = strtok(szSrc, szSeps);

     这时默认的strtok内部下一个指针地址指向了char szSrc[128] = "THIS,IS,A,TEST";的字符串"IS,A,TEST"

    接下来线程2开始执行, char *pToken = strtok(szSrc, szSeps);

     这时默认的strtok内部下一个指针地址指向了char szSrc[128] = "HELLO WORLD THIS IS STRTOK TEST";的字符

串" WORLD THIS IS STRTOK TEST"

       那么接下来会发生什么奇怪的现象呢?

      Thread1 执行strtok(NULL, ",")由于分析的源串已经指向到了" WORLD THIS IS STRTOK TEST",所以将不能再分析得到任何以逗号(,)分隔的字串了!线程Thread2由于指向不变,仍然可以分析得到以空格( )分隔的字符串。

 

      这个问题说明了在strtok是一个全局函数,里面使用了一个静态地址指针,这样的话,多线程调用就随时会改变静态指针的指向,程序运行的结果也就未为可知,不是程序本身希望实现的功能。

 

      所以,在多线程程序里面,最好不要使用strtok函数!使用就得冒很大的程序运行结果不正确的风险

 

      这里我写了一个多线程安全的TStrTok函数,可以实现字符串的分隔,基本原理是:将分隔字符填0,然后提取各个字符串。

      下面是源码:

  1. #include <afxtempl.h>
  2. #include <iostream>
  3. using namespace std;
  4. /*--------------------------------------------------------------------------
  5.   - src (in) :  源串
  6.   - sep (in) :  分隔串
  7.   - srcLen (in):    源串长度
  8.   - sepLen (in):  分隔串长度
  9.   - sl     (out):  分析结果集字符串
  10. *--------------------------------------------------------------------------*/
  11. BOOL MStrTok(const char* src, const char *sep, int srcLen, int sepLen, CStringArray &sa)
  12. {
  13.     if( (src == NULL) || (sep == NULL) )
  14.     {
  15.         return FALSE;
  16.     }
  17.     
  18.     int nMinLenSrc = min(strlen(src), srcLen);
  19.     int nMinLensep = min(strlen(sep), sepLen);
  20.     
  21.     char *pNew = new char[nMinLenSrc + 1];
  22.     memset(pNew, 0, nMinLenSrc + 1);
  23.     memcpy(pNew, src, nMinLenSrc);
  24.     
  25.     char *p = (char *)sep;
  26.     char *q = (char *)pNew;
  27.     int i = 0;
  28.     int j = 0;
  29.     while(*p != 0 && i < nMinLensep)
  30.     {
  31.         q = pNew;
  32.         for(j = 0; j < nMinLenSrc; j++)
  33.         {
  34.             if(*q == *p)
  35.                 *q = 0;
  36.             q++;
  37.         }
  38.         
  39.         p++;
  40.         i++;
  41.     }
  42.     
  43.     q = pNew;
  44.     i = 0;
  45.     while(i < nMinLenSrc)
  46.     {
  47.         if(*q != 0)
  48.         {
  49.             sa.Add(q);
  50.             
  51.             i += strlen(q);
  52.             q = pNew + i;
  53.         }
  54.         else
  55.         {
  56.             i++;
  57.             q++;
  58.         }
  59.     }
  60.     
  61.     delete []pNew;
  62.     
  63.     return TRUE;
  64. }
  65. int main()
  66. {
  67.     char szTest[] = "THIS IS A   ;    ,,,,, TStrTok,,,   TEST,        OK    ;NOW;;;;;;???";
  68.     char szSeps[] = ",; ";
  69.     CStringArray sa;
  70.     MStrTok(szTest, szSeps, sizeof(szTest), sizeof(szSeps), sa);
  71.     cout << endl;
  72.     for(int i = 0; i < sa.GetSize(); i++)
  73.     {
  74.         cout << (LPCTSTR)sa[i] << " ";
  75.     }
  76.     cout << endl;
  77.     getchar();
  78.     return 0;
  79. }
  80. /*
  81.   运行结果如下:
  82.   
  83.   THIS IS A TStrTok TEST OK NOW ???
  84.  */

----------------------------------------------------------------------------------------------------------------------------------------

【后记】:

      本文发表以后,很多网友提出了很多很好的意见和建议。我查了一下strtok_r这个多线程的字符串分割

函数,发现在VC下无定义;在Unix下倒是可以用,但是Unix文档有说明,该函数会改写源串,不建议使用。

      另外,我希望一次将所有的字符串都分割出来,而不是一次次的调用,所以,我还是保留我的MStrTok

的定义方式,和原始的strtok是有差别的,使用时请注意。

      MStrTok的输出是VC的CStringArray,如果要用标准C++,可以用vector代替CStringArray,在此不再赘述。

     原来的MStrTok效率可以改进,一次扫描足以得到结果,下面是改进后的MStrTok:

 

  1. BOOL MStrTok(const char* src, const char *sep, int srcLen, int sepLen, CStringArray &sa)
  2. {
  3.     if( (src == NULL) || (sep == NULL) )
  4.     {
  5.         return FALSE;
  6.     }
  7.     
  8.     int nMinLenSrc = min(strlen(src), srcLen);
  9.     int nMinLensep = min(strlen(sep), sepLen);
  10.     
  11.     char *pNew = new char[nMinLenSrc + 1];
  12.     memset(pNew, 0, nMinLenSrc + 1);
  13.     memcpy(pNew, src, nMinLenSrc);
  14.         
  15.     char *p = (char *)pNew;
  16.     char c;
  17.     int i = 0, n = 0;
  18.     char *pStr = NULL;
  19.     while(*p != 0 && n < nMinLenSrc)
  20.     {           
  21.         c = *sep;
  22.         for(i = 0; (i < nMinLensep) && (c != 0); i++)
  23.         {
  24.             c = *(sep + i);
  25.             if(c == *p)
  26.             {
  27.                 *p = 0;
  28.             }
  29.         }   
  30.         
  31.         if(*p == 0)
  32.         {
  33.             if(pStr != NULL)
  34.             {
  35.                 sa.Add(pStr);
  36.                 pStr = NULL;
  37.             }
  38.         }
  39.         else 
  40.         {
  41.             if (pStr == NULL) 
  42.             {
  43.                 pStr = p;               
  44.             }           
  45.         }
  46.         
  47.         p++;
  48.         n++;
  49.     }
  50.     if(pStr != NULL)
  51.     {
  52.         sa.Add(pStr);
  53.     }
  54.     
  55.     delete []pNew;  
  56.     return TRUE;
  57. }
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 23
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值