strtok函数缺陷再探

简介:     要将类似“a a a,b b b,c c c,d d d,e e e,f f f”这样的字符串一一分割出来,如何实现? 1、使用strtok()存在的缺陷     strtok()函数嵌套使用,无论是在同一个函数中或者在主调和被调函数中同时使用strtok的开始语句如strtok(str,","); 都极易出现问题。

    要将类似“a a a,b b b,c c c,d d d,e e e,f f f”这样的字符串一一分割出来,如何实现?


1、使用strtok()存在的缺陷

    strtok()函数嵌套使用,无论是在同一个函数中或者在主调和被调函数中同时使用strtok的开始语句如strtok(str,","); 都极易出现问题。看下面案例:


  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3.     char buffer[] = "a a a,b b b,c c c,d d d,e e e,f f f";
  4.     char *buf = buffer;
  5.     char *p[30];
  6.     int i = 0;
  7.     printf("Before strtok,len of buffer = %d\n",strlen(buffer));

  8.     while((p[i]=strtok(buf,",")) != NULL)
  9.     {
  10.         buf = p[i];
  11.         printf("%s\n",buf);
  12.         while( (p[i]=strtok(buf," ")) != NULL )
  13.         {
  14.             printf("%s\n",p[i]);
  15.             i++;
  16.             buf = NULL;
  17.         }
  18.         printf("========================\n");
  19.         buf = NULL;
  20.     }

  21.     printf("after strtok,len of buffer = %d",strlen(buffer));
  22.     getchar();
  23.     return 0;
  24. }


上面代码的运行效果如下:

image图1

 

    由上面的运行效果可以看出,这个并不是我们想要的效果。因为它只分割了buffer数组第一个逗号前面的字符串。

    很明显,因为内循环中使用了strtok同时也使用了静态指针变量,使得内循环执行结束时,外循环的strtok函数给静态变量所赋的值被修改了;而我们想要的是第一次内循环执行结束时,这个静态变量应该存储的是字符串”b b b,c c c,d d d,e e e,f f f”的首地址。

image 图2 外循环未进行分割前

 

image

图3 外循环进行一次分割

 

image

图4 内循环运行结束

 

    注意,虽然号称为分割,实际上整个buffer的存储空间内初始元素是不变的。分割的实质利用的一个向前移动的分割指针,凡是遇到分割符,就将该置置\0并返回分割符前面的字符串指针,图6证明了这个说法。上面程序中,对buffer执行一次外循环、一次内循环前后的对比结果,见图5:

image图5

 

image

图6


问题:为什么外循环只执行了一次就结束了呢?

    我们假设,strtok需要与一个静态的指针变量一起搭配使用。上面代码所使用使用了两套strtok,而保存地址的指针变量只有一个。当外循环第一次执行时,指针变量存储的是字符’b’所在的地址。而因为下面又执行了内循环,当内循环的strtok执行结束,指针变量此时存储的是”a\0a\0a\0”,第三个’a’的地址。

    当外循环的strtok从第三个’a’地址开始往后遍历时,刚一移动,就遇到了”\0”,没办法。
    所谓出师未捷身先死,就出现本例图1所示的效果了。


 

2、使用strtok_s()函数(linux下是strtok_r())就能实现我们想要的效果


  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3.     char buffer[] = "a a a,b b b,c c c,d d d,e e e,f f f";
  4.     char *buf = buffer;
  5.     char *p[30];
  6.     int i = 0;
  7.     char *outer_ptr = NULL;
  8.     char *inner_ptr = NULL;
  9.     printf("Before strtok,len of buffer = %d\n",strlen(buffer));

  10.     while((p[i]=strtok_s(buf,",",&outer_ptr)) != NULL)
  11.     {
  12.         buf = p[i];
  13.         printf("%s\n",buf);
  14.         while( (p[i]=strtok_s(buf," ",&inner_ptr)) != NULL )
  15.         {
  16.             printf("%s\n",p[i]);
  17.             i++;
  18.             buf = NULL;
  19.         }
  20.         printf("========================\n");
  21.         buf = NULL;
  22.     }

  23.     printf("after strtok,len of buffer = %d",strlen(buffer));
  24.     getchar();
  25.     return 0;
  26. }


image图7

 

strtok_s()函数是如何实现这个功能的呢?这又如何解释呢?

image

图8

    由图8,因为每个strtok各自维护一个pointer,所以无论进行分割后进行什么操作,这些pointer都会很忠实的记住下一次开始的地址。从而避免出现1中的由于strtok嵌套使用引入的问题。

 

 

参考博客:

http://www.cnblogs.com/hoys/archive/2011/09/19/2180999.html

PS: 参考博客的博主hoy同时也是在下的老友,向hoy的探索和贡献精神致敬!

相关文章
|
存储 监控 Linux
【Linux IO多路复用 】 Linux下select函数全解析:驾驭I-O复用的高效之道
【Linux IO多路复用 】 Linux下select函数全解析:驾驭I-O复用的高效之道
2373 0
|
Rust 安全 程序员
Rust与C++:内存管理与安全性的比较
本文将对Rust和C++两种编程语言在内存管理和安全性方面进行深入比较。我们将探讨Rust如何通过其独特的所有权系统和生命周期管理来消除内存泄漏和悬挂指针等常见问题,并对比C++在这方面的挑战。此外,我们还将讨论Rust的类型系统和编译器如何在编译时捕获许多常见的运行时错误,从而提高代码的安全性。
|
关系型数据库 MySQL Java
Sakai-21部署
记录了两种部署方式: ① 二进制部署 ② 源文件部署
Sakai-21部署
|
编解码 安全 Android开发
低功耗蓝牙LE Audio Profile 详细介绍
2019年底,蓝牙官方组织SIG发布了蓝牙5.2版本的核心协议,其中增加了一个重要的特性---LE Audio。蓝牙的应用协议都是从应用层到物理层完整包含的协议,LE Audio也不例外。但蓝牙5.2核心协议仅仅定义了蓝牙LE的链路层传输Audio的方式,上层协议以及完整的LE Audio规范迟迟未出,近日,蓝牙官方组织释放了LE Audio较为完整的规范文档。
低功耗蓝牙LE Audio Profile 详细介绍
|
分布式计算 关系型数据库 Ruby
crash命令 —— rd
crash命令 —— rd
下载python所有的包 国内地址
下载python所有的包 国内地址
|
SQL 移动开发 Linux
linux下find、grep命令详解
linux下find、grep命令详解
357 8
在Linux中,如何查看某进程所打开的所有文件?
在Linux中,如何查看某进程所打开的所有文件?
Outlook邮箱怎么建立邮件组?
在Outlook中创建邮件组,登录邮箱后点击“联系人”,选择“新建联系人组”,命名并添加成员,保存即成。发邮件时直接写邮件组名,Outlook会自动填充成员。可编辑或删除组,高效管理邮件收发。