到处是“坑”的strtok()—解读strtok()的隐含特性

2023-10-26

在用C/C++实现字符串处理逻辑时,strtok函数的使用非常广泛,其主要作用是按照给定的字符集分隔字符串,并返回各子字符串。由于该函数的使用有诸多限制,如果使用不当就会造成很多“坑”,因此本文首先介绍那些经常误踩的坑,然后通过分析源代码,解读该函数的诸多隐含特性,以便对该函数有个全面的理解,不再被坑。

那些年一起踩过的坑

TOP1 不可重入

目前大部分程序都是在多线程环境下运行的,因此函数的可重入性就显得尤为重要。下面实例的本意是,先按照;分隔句子并输出,然后再按照空格分隔单词并输出,可结果呢?

#include <stdio.h>
#include <string.h>

int main(void)
{
    char  szTest[]  = "Hello world;I'm Pele"; /* 以;作为句子的分隔符,以空格作为单词的分隔符 */
    char *pSentence = NULL;
    char *pWord     = NULL;

    /* 先分隔句子 */
    pSentence = strtok(szTest, ";");
    while (NULL != pSentence)
    {
        printf("The sentence is %s.\n", pSentence);

        /* 再分隔单词 */
        pWord = strtok(pSentence, " ");
        while (NULL != pWord)
        {
            printf("The word is %s.\n", pWord);
            pWord = strtok(NULL, " ");
        }

        pSentence = strtok(NULL, ";");
    }

    return 0;
}

预期结果如下:

$ gcc test_strtok.c
$ ./a.out
The sentence is Hello world.
The word is Hello.
The word is world.
The sentence is I'm Pele.
The word is I'm.
The word is Pele.

可实际结果如下:

$ gcc test_strtok.c
$ ./a.out
The sentence is Hello world.
The word is Hello.
The word is world.

实际告诉我们:strtok不可重入

TOP2 源字符串会被修改

如果一个字符串在我们的视线之外被修改了,那么可能会发生一些列诡异的事,而我们却全然不知。请看如下案例:

#include <stdio.h>
#include <string.h>

int main(void)
{
    char  szTest[]  = "Hello world;I'm Pele";
    char *pSentence = NULL;

    printf("The original string is %s.\n", szTest);

    pSentence = strtok(szTest, ";");
    while (NULL != pSentence)
    {
        pSentence = strtok(NULL, ";");
    }

    printf("The final string is %s.\n", szTest);

    return 0;
}

预期结果如下:

$ gcc test_strtok.c
$ ./a.out
The original string is Hello world;I'm Pele.
The final string is Hello world;I'm Pele.

可实际结果如下:

$ gcc test_strtok.c
$ ./a.out
The original string is Hello world;I'm Pele.
The final string is Hello world.

实际告诉我们:你传入的字符串会被strtok修改

TOP3 连续的分隔符被当做一个分隔符处理

如果两个分隔符连续出现,那么在分隔的时候,你是希望分隔出一个空字符串,还是希望strtok忽略掉多余的分隔符呢?请看strtok给我们的答案:

#include <stdio.h>
#include <string.h>

int main(void)
{
    char  szTest[]  = "Hello world;;I'm Pele";  /* 连续使用两个;分隔语句 */
    char *pSentence = NULL;

    pSentence = strtok(szTest, ";");
    while (NULL != pSentence)
    {
        printf("The sentence is %s.\n", pSentence);
        pSentence = strtok(NULL, ";");
    }

    return 0;
}

实际结果如下:

$ gcc test_strtok.c
$ ./a.out
The sentence is Hello world.
The sentence is I'm Pele.

实际告诉我们:连续出现的分隔符只被处理一次

TOP4 字符串首尾的分隔符会被忽略

你希望将字符串首尾的分隔符忽略掉吗?如果不希望,那么请慎用strtok,请看:

#include <stdio.h>
#include <string.h>

int main(void)
{
    char  szTest[]  = ";Hello world;I'm Pele;;";
    char *pSentence = NULL;

    pSentence = strtok(szTest, ";");
    while (NULL != pSentence)
    {
        printf("The sentence is %s.\n", pSentence);
        pSentence = strtok(NULL, ";");
    }

    return 0;
}

实际结果如下:

$ gcc test_strtok.c
$ ./a.out
The sentence is Hello world.
The sentence is I'm Pele.

实际告诉我们:字符串首尾的分隔符会被忽略

真相只有一个

下面的代码取自glibc-2.20的strtok.c文件,未做任何删改,中文注释为笔者添加,用于说明造成以上坑的原因。

#include <string.h>


static char *olds; /* 使用了全局变量,因此该函数不可重入--TOP1坑 */

#undef strtok

#ifndef STRTOK
# define STRTOK strtok
#endif

/* Parse S into tokens separated by characters in DELIM.
   If S is NULL, the last string strtok() was called with is
   used.  For example:
    char s[] = "-abc-=-def";
    x = strtok(s, "-");     // x = "abc"
    x = strtok(NULL, "-=");     // x = "def"
    x = strtok(NULL, "=");      // x = NULL
        // s = "abc\0=-def\0"
*/
char *
STRTOK (char *s, const char *delim)
{
  char *token;

  if (s == NULL)
    s = olds;

  /* 跳过了字符串前面的分隔符,如果字符串只剩下尾部的分隔符,跳过前导符相当于忽略尾部的分隔符--TOP4坑 */
  /* Scan leading delimiters.  */  
  s += strspn (s, delim);
  if (*s == '\0')
    {
      olds = s;
      return NULL;
    }

  /* Find the end of the token.  */
  token = s;
  s = strpbrk (token, delim);  
  if (s == NULL)
    /* This token finishes the string.  */
    olds = __rawmemchr (token, '\0');
  else /* 找到一个分隔符就返回,下次进入该函数会跳过前导分隔符,此为TOP3坑 */
    {      
      /* Terminate the token and make OLDS point past it.  */
      *s = '\0';        /* 将分隔符所在位置置0,此为TOP2坑 */
      olds = s + 1;
    }
  return token;
}

总结

  • 尽量使用可重入版的strtok,Windows平台下为strtok_s,Linux平台下为strtok_r
  • 牢记strtok函数族的分隔规则:忽略字符串前后的分隔符,连续的分隔符被当做一个处理
  • 在使用strtok前,请对源字符串进行备份,除非你可以接受字符串被修改这一事实。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

到处是“坑”的strtok()—解读strtok()的隐含特性 的相关文章

  • 当父类也实现 IDisposable 时,在子类上实现 IDisposable

    我有一个父类和子类都需要实现IDisposable 应该在哪里virtual and base Dispose 通话发挥作用 当我刚刚覆盖Dispose bool disposing 打电话 说我实现了感觉真的很奇怪IDisposable没
  • 将 Azure Blob 与 Azure 网站连接

    我正在尝试将 Azure 网站连接到 Azure blob 我打算在容器中托管一些文件 然后从我的网站获取它们 我从本教程开始 http azure microsoft com en us documentation articles we
  • 如何显示图片目录中的图像?

    我想显示图片库中的图片 我获取图片并绑定数据 StorageFolder picturesFolder KnownFolders PicturesLibrary IReadOnlyList
  • 修改 CookieAuthenticationOptions LoginPath OnRedirectToReturnUrl 事件

    我的 MVC 6 ASP NET 5 项目中有以下设置 配置方法中的Startup cs app UseCookieAuthentication options gt options AuthenticationScheme Cookie
  • 如何将 Boost 库添加到 XCode 6.0 中的 C++ 程序?

    我在用着XCode6 0并且需 要boost程序库 我已经下载了boost 1 57 0 tar gz from http sourceforge net projects boost files boost 1 57 0 http sou
  • 如何设置 QTableView 中特定单元格的线条样式?

    我正在使用 QT GUI 我正在使用 QTableView 实现一个简单的十六进制编辑控件 我最初的想法是使用一个有十七列的表格 表的每一行都有 16 个十六进制字节 然后在第十七列中显示该数据的 ASCII 表示形式 理想情况下 我想编辑
  • C++变量声明和初始化规则

    考虑以下声明和初始化类型变量的方法C C c1 C c2 c2 C C c3 C C c4 C 所有这些是否完全等同 或者其中一些可以根据确切的定义而有所不同C 假设它有公共默认值和复制构造函数 这些意味着 C c1 default con
  • C# 中是否有相当于 php array_merge 的函数

    如果不是 创建它的最佳方法是什么 注意 合并不仅仅是附加 它融合了相同的键 此功能存在于 List 元素上 在 C 中 数组是固定宽度的项 因此在不创建新数组的情况下无法修改大小 然而 列表却是另一回事 你可以做 List
  • .net 日历 - 使整个单元执行回发(可点击)

    我已经启动并运行了一个 net 日历 并从数据库中获取信息 默认情况下 天数会应用回发操作 我想做的是将该操作应用于整个单元格 这样用户就不需要仅单击文本链接 我是 dayRenderer 操作 我有以下行来尝试复制该操作 但第二个参数我不
  • C#等待串口数据

    我试图通过 C 应用程序从指纹扫描仪获取数据 但在指纹发送之前 我的整个代码都会执行 我尝试使用延迟功能System Threading Thread Sleep 1000 因此它可以在下一步执行之前获取数据 但这一切似乎都是徒劳的 任何人
  • C# 从视频文件的一部分中提取帧

    使用 AForge ffmpeg 包装器 您可以使用 VideoFileReader 类从视频中提取帧并将其保存为位图 请参阅以下示例 提取 avi 文件的帧 https stackoverflow com questions 178256
  • HttpCookie 和 Cookie 的区别?

    所以我很困惑 因为 msdn 和其他教程告诉我使用 HttpCookies 通过 Response Cookies Add cookie 添加 cookie 但这就是问题所在 Response Cookies Add 只接受 Cookie
  • L"" 和 u8"" 之间的区别

    以下有什么区别吗 auto s1 L 你好 auto s2 u8 你好 Are s1 and s2指的是同一类型 如果不是 有什么区别以及首选哪一个 它们不是同一类型 s2是 UTF 8 或窄字符串文字 这C 11标准草案 http www
  • Mono 的 DNS 刷新超时

    虽然目前Mono项目的ServicePointManager类有DnsRefreshTimeout属性启用到其接口中 相关属性未实现 调用示例 ServicePointManager DnsRefreshTimeout 10 60 1000
  • IE 中“对象不支持属性或方法‘查找’”

  • 如何分配二维数组? [复制]

    这个问题在这里已经有答案了 我需要创建一个二维数组 目前我将其创建为int a 100 100 但我需要使用动态分配内存malloc在C语言中 我用了代码 include
  • 如何在 .net 表单应用程序的消息框中创建自定义按钮?

    我正在尝试在表单应用程序上使用 NET Compact Framework 3 5 实现自定义消息框 确定 取消 我如何实施它 如果您正在寻找带有 确定 和 取消 按钮的消息框 您可以使用 MessageBox Show this Mess
  • 如何将格式化的电子邮件地址解析为显示名称和电子邮件地址?

    给定电子邮件地址 Jim 电子邮件受保护 gt 如果我尝试将其传递给 MailAddress 我会得到异常 指定的字符串不符合电子邮件地址所需的格式 如何将此地址解析为显示名称 Jim 和电子邮件地址 电子邮件受保护 cdn cgi l e
  • 链接的 ostream 内部行为及其在 MSVC 上的结果(与 Clang 相比)

    MSVC 与 GCC Clang 的流 内部字符串和操作排序问题 大家好 我最近刚刚开始更认真地使用 MSVC 来完成我的一个跨平台项目 同时通过以下方式测试输出chainedSTD 流 IE 一系列的obj foo lt lt endl
  • 如何解析 XML diff 以仅显示差异

    我使用以下方法比较两个序列化对象的 xmlMS XmlDiffPatch 工具 http msdn microsoft com en us library aa302294 aspx C XML 示例 1

随机推荐

  • Android Animation.setAnimationListener()失效问题

    Android执行动画 使用Animation情景如下 Animation animation new Animation 如果需要监听动画执行 animation setAnimationListener 需要在 view startAn
  • Conda错误:Collecting package metadata (current_repodata.json): failed

    conda新安装设置清华源后发现并没有使用 且会出现错误 Collecting package metadata current repodata json failed 换了科大源也没成功 考虑可能是默认源的问题 删除 condarc文件
  • TCP/IP详解学习笔记(4)-ICMP协议,ping和Traceroute

    1 IMCP协议介绍 前面讲到了 IP协议并不是一个可靠的协议 它不保证数据被送达 那么 自然的 保证数据送达的工作应该由其他的模块来完成 其中一个重要的模块就是ICMP 网络控制报文 协议 当传送IP数据包发生错误 比如主机不可达 路由不
  • STM32F103ZET6【HAL函开发】STM32CUBEMX------1.GPIO输出-点亮led灯

    一 硬件介绍 正点原子战舰开发板 主控芯片STM32F103ZET6 两个LED分别连接到单片机的PB5和PE5 二 STM32CUBEMX基础配置 2 1 晶振配置 如果你的板子上外部高速晶振8M和外部低速晶振32 768K都有的话 那么
  • Java中如何自定义数组

    Java中如何自定义数组 数组是一种非常常见的数据结构 在Java中也是一个非常重要的概念 在Java中 数组的定义和使用非常简单 但是如果我们想要自定义数组 那么可能需要一些额外的操作 Java中如何自定义数组 在Java中 数组是一种简
  • 华为OD机试 - 分苹果(Java)

    题目描述 A B两个人把苹果分为两堆 A希望按照他的计算规则等分苹果 他的计算规则是按照二进制加法计算 并且不计算进位 12 5 9 1100 0101 9 B的计算规则是十进制加法 包括正常进位 B希望在满足A的情况下获取苹果重量最多 输
  • 【转载】区块链技术原理、应用领域及挑战

    区块链技术原理 应用领域及挑战 李董 魏进武 中国联合网络通信有限公司研究院 北京 100032 引用本文 李董 魏进武 区块链技术原理 应用领域及挑战 电信科学 J 2016 32 12 20 26 doi 10 11959 j issn
  • 小米手机解BL锁教程

    1 找到设置 找到我的设备 2 点击全部参数 多点几下miui版本 直到弹出开发者模式提醒 3 返回 找到更多设置 4 找到开发者选项
  • Linux设备上时间不准确?使用chrony服务配置时间服务器实现Linux时间同步以及实现主从设备时间同步

    本文基于Linux上CentOS 7版本配合chrony 需要使用yum自行下载 进行演示 目录 一 计算机设备上的两种时间 1 硬件时间 2 系统时间 二 配置同步时间服务器 1 安装服务 2 配置服务 三 搭建主从时间服务器 1 服务器
  • 阿里云提示ECS服务器存在漏洞处理方法

    1 阿里云提供生成修复命令 但是这个只提供给企业版 即收费的 2 自己手动修复的话 采用软件升级一般都可以解决 除了提示带kernel的高危漏洞的 其他的不需要重启实例即可修复 有kernel的需要更新完成重启实例 这里可以先把 漏洞名称
  • 2021-04-08 使用Eclipse进行Web前端开发

    使用Eclipse进行Web前端开发 前言 本机为微软Surface pro4 为64位 所用操作系统为Windos 10 使用的Java版本为1 8 0 151 使用的JDK版本为JDK8 注意事项 1 Eclipse安装插件的时候一定要
  • 【mac】Mac 安装 RabbitMQ

    文章目录 1 概述 2 安装brew 3 安装 4 安装RabiitMQ的可视化监控插件 5 配置环境变量 6 后台启动 rabbitMQ 7 创建rabbitmq账号 8 给账号配置角色 1 概述 学习spring cloud 的时候 因
  • 【pytorch】pytorch模型保存技巧

    Pytorch会把模型相关信息保存为一个字典结构的数据 以用于继续训练或者推理 1 保存与加载模型参数 这是最常见的模型保存与加载方式 保存方式如下 state model state dict torch save state xxx p
  • qml实现红绿灯切换功能

    题目要求 参考代码 https download csdn net download y478225902 5260541 实现源码 import QtQuick 2 12 import QtQuick Window 2 12 Window
  • springboot整合maven Profile实现properties文件多环境配置

    步骤 首先写几个properties的配置文件 一般这样的文件有三个 而且文件的名称也也可以随意 不论你们的项目是使用的springmvc还是springboot 文件名称都可以随意指定 例如我的几个文件 在文件中写一些测试的属性值 方便测
  • 【一】重温HTML

    引言 经典对答 面试官 你了解HTML吗 回答 啊 我是来面试前端的呀 我会Vue 面试官 写文思考 写这一系列文章的时候 自己思考了几个问题 HTML的文章太多了 为什么还要写 HTML的入门谁不会 还要学 HTML的文章基本都是水文 谁
  • ES6解构赋值

    前面的话 我们经常定义许多对象和数组 然后有组织地从中提取相关的信息片段 在ES6中添加了可以简化这种任务的新特性 解构 解构是一种打破数据结构 将其拆分为更小部分的过程 本文将详细介绍ES6解构赋值 引入 在ES5中 开发者们为了从对象和
  • Mysql中MVCC的使用及原理详解

    准备 测试环境 Mysql 5 7 20 log 数据库默认隔离级别 RR Repeatable Read 可重复读 MVCC主要适用于Mysql的RC RR隔离级别 创建一张存储引擎为testmvcc的表 sql为 CREATE TABL
  • error compiling template但编辑器内未报错,处理步骤。

    1 首先寻找自己所引入的组件当中 例如用到了某个方法 而自己没有把方法写上 2 寻找自己所引入的代码当中是否有重复的代码 可能是复制的时候多复制一行而导致的 3 寻找是否有空格所导致的error compiling template 报错
  • 到处是“坑”的strtok()—解读strtok()的隐含特性

    在用C C 实现字符串处理逻辑时 strtok函数的使用非常广泛 其主要作用是按照给定的字符集分隔字符串 并返回各子字符串 由于该函数的使用有诸多限制 如果使用不当就会造成很多 坑 因此本文首先介绍那些经常误踩的坑 然后通过分析源代码 解读