strtok函数线程安全吗?如何避免内存泄漏?

99ANYc3cd6
预计阅读时长 16 分钟
位置: 首页 C语言 正文

函数简介

strtok 是 C 标准库 <string.h> 中的一个函数,它的全称是 "string token"(字符串标记),它的主要作用是:将一个字符串按照指定的分隔符(一个或多个字符)进行分割,并逐个返回分割后的子串(标记)

c语言strtok函数
(图片来源网络,侵删)

函数原型

#include <string.h>
char *strtok(char *str, const char *delim);

参数说明

  1. char *str:

    • 首次调用时:指向你想要分割的源字符串,函数会在这个字符串中查找标记,并对其进行修改(非常重要!)。
    • 后续调用时:必须传入 NULL,这告诉 strtok 继续从上一次分割的位置开始处理同一个字符串。
  2. const char *delim:

    • 这是一个分隔符字符串,它包含了所有可以作为分隔符的字符。
    • 表示空格、逗号和句号都可以作为分隔符。
    • strtok 会将 一个或多个连续 的分隔符视为一个分隔符。

返回值

  • 成功时:返回一个指向当前找到的标记(子串)的 char* 指针。
  • 当字符串中所有的标记都被找到后,再次调用会返回 NULL
  • strdelim 都是 NULL,或者 str 是空字符串 ,行为是未定义的。

工作原理(非常重要!)

理解 strtok 的工作原理是正确使用它的关键,它依赖于一个静态的、内部的指针来记录其状态。

  1. 首次调用

    c语言strtok函数
    (图片来源网络,侵删)
    • strtok 接收源字符串 str
    • 它会跳过开头的所有分隔符。
    • 然后找到标记的起始位置,并开始向后扫描,直到遇到一个分隔符或字符串的结尾 \0
    • 关键操作:在遇到分隔符的地方,它会用 \0 (字符串结束符) 来覆盖它,从而将源字符串“分割”成多个独立的字符串
    • 它返回指向这个新创建的子串的指针。
    • 它会内部记录下下一个应该开始查找的位置(即 \0 后面的那个字符)。
  2. 后续调用

    • 当你传入 NULL 作为第一个参数时,strtok 知道这不是一个新字符串,而是要继续上一次的工作。
    • 它会从内部记录的位置开始继续扫描。
    • 同样,它会跳过所有连续的分隔符,找到下一个标记,用 \0 截断,并返回指针。
    • 这个过程会一直持续,直到字符串的末尾,此时它会返回 NULL

代码示例

下面的例子清晰地展示了如何使用 strtok 来分割一个由逗号和空格组成的字符串。

#include <stdio.h>
#include <string.h>
int main() {
    char str[] = "apple,banana orange,grape";
    const char *delim = ", "; // 分隔符是逗号或空格
    // 首次调用,传入源字符串
    char *token = strtok(str, delim);
    // 循环获取所有标记,直到 strtok 返回 NULL
    while (token != NULL) {
        printf("Token: %s\n", token);
        // 后续调用,传入 NULL
        token = strtok(NULL, delim);
    }
    return 0;
}

输出结果:

Token: apple
Token: banana
Token: orange
Token: grape

关键点与注意事项

1 原地修改字符串

strtok 会直接修改源字符串,在上面的例子中,执行完 strtok 后,str 数组的内容已经发生了改变,它实际上变成了这样: "apple\0banana\0orange\0grape\0"

这意味着:

  • 不能将 strtok 应用于字符串字面量(常量字符串),因为尝试修改常量会导致未定义行为(通常是程序崩溃)。
    // 错误示例!
    char *str = "hello, world"; // str 指向只读内存区
    strtok(str, ", "); // 尝试修改只读内存,崩溃!
  • 如果你需要保留原始字符串,必须先创建一个副本,然后对副本进行操作。

2 线程安全问题

strtok 使用一个静态的内部指针来记录状态,这意味着strtok 不是线程安全的,如果在多线程环境中同时调用 strtok 来分割不同的字符串,它们会互相干扰,导致不可预期的结果。

在多线程环境下,应该使用 strtok_r(POSIX 标准,Linux/Unix/macOS 支持)或 strtok_s(C11 标准,Windows 支持),这两个函数是 strtok 的线程安全版本,它们通过一个额外的参数让调用者自己管理状态指针。

3 连续的分隔符

strtok 会将一个或多个连续的分隔符视为一个分隔符,对于字符串 "a,,b",使用 作为分隔符,会得到 "a""b",中间不会出现空的标记。

4 无法识别空字符串作为标记

由于 strtok 会跳过开头的所有分隔符,它无法识别开头或结尾的空标记,对于字符串 ",a,b,",使用 作为分隔符,只会得到 "a""b",开头的和结尾的空标记会被忽略。


strtok 的现代替代方案

由于 strtok 有诸多限制(非线程安全、修改源字符串、无法处理空标记等),在现代C++或需要更健壮代码的C项目中,通常会使用更安全、更灵活的替代方案。

1 C++ 的 std::stringstream

这是在C++中最推荐、最安全、最优雅的方式。

#include <iostream>
#include <string>
#include <sstream>
int main() {
    std::string text = "  apple, banana ,orange,  ";
    std::stringstream ss(text);
    std::string token;
    char delimiter = ','; // 可以是任意字符
    while (std::getline(ss, token, delimiter)) {
        // std::getline 不会自动去除空格,可以手动处理
        size_t start = token.find_first_not_of(" ");
        if (start != std::string::npos) {
            token = token.substr(start);
            std::cout << "Token: " << token << std::endl;
        }
    }
    return 0;
}

2 C++ 的 std::string::find

手动使用 std::stringfindsubstr 方法可以实现完全自定义的分割逻辑,更灵活。

3 C11 的 strtok_s

如果你必须在C语言环境中工作,并且编译器支持C11标准,strtok_s 是比 strtok 更好的选择。

#include <stdio.h>
#include <string.h>
int main() {
    char str[] = "apple,banana orange,grape";
    const char *delim = ", ";
    char *context = NULL; // 用于保存状态的上下文指针
    char *token;
    // 首次调用,context 传入 NULL
    token = strtok_s(str, delim, &context);
    while (token != NULL) {
        printf("Token: %s\n", token);
        // 后续调用,传入 context
        token = strtok_s(NULL, delim, &context);
    }
    return 0;
}

特性 strtok strtok_r / strtok_s std::stringstream (C++)
线程安全
修改源字符串
处理空标记 不能 不能 可以(通过逻辑实现)
易用性 简单 中等 非常简单
适用标准 C89/C90 POSIX (C) / C11 (Windows) C++
推荐场景 快速脚本、非关键代码、旧项目兼容 多线程C代码 现代C++项目

核心建议:

  • 在C++中,永远优先使用 std::stringstream 或其他STL工具。
  • 在C语言中,如果可能,尽量使用 strtok_s(Windows)或 strtok_r(Linux/Unix)。
  • 仅在单线程、临时性、不介意修改源字符串的简单场景下,才考虑使用 strtok
-- 展开阅读全文 --
头像
织梦留言簿模块下载地址在哪?
« 上一篇 今天
dede调用全部分类
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

目录[+]