strtok r:include
深入理解 strtok_r:线程安全的字符串分割函数
在 C 语言编程中,处理字符串是常见的任务,而将一个字符串根据特定分隔符分割成多个子串,是字符串处理中非常基础且常用的操作,标准库函数 strtok() 提供了便捷的实现方式,但其在多线程环境下的使用存在隐患,为了满足多线程编程的需求,POSIX 标准引入了 strtok_r() 函数,本文将深入探讨 strtok_r 函数,理解其工作原理、与 strtok 的区别,并展示其在多线程安全场景下的应用。
strtok_r 是什么?
strtok_r 是 C 标准库(在支持 POSIX 的系统上)提供的一个函数,用于将一个字符串按照指定的分隔符集(delimiters)分割成一系列的子字符串,它被称为 strtok 的“线程安全”版本。
函数原型通常如下(具体细节可能因系统而异,但基本形式如此):
char *strtok_r(char *str, const char *delimiters, char **saveptr);
参数说明:
str: 指向要被分割的字符串的指针,这是该函数调用序列中的第一个字符串,在后续的调用中,str为NULL,则函数会继续处理上一次调用的剩余部分。delimiters: 指向一个字符数组的指针,该数组包含所有用作分隔符的字符,字符串中的任何这些字符都会将字符串分割开。saveptr: 一个指向char*指针的指针,这个参数用于在函数调用之间保存状态,使得strtok_r能够记住上一次分割的位置,这是strtok_r实现线程安全的关键。
返回值:

- 成功时,返回下一个找到的、由分隔符分隔开的子字符串的起始地址。
- 如果没有找到任何子字符串,则返回
NULL。
strtok_r 如何工作?
strtok_r 的工作方式与 strtok 类似,但使用 saveptr 参数来维护状态,调用者需要提供一个 saveptr 变量(通常是一个 char * 类型的指针),并在每次调用 strtok_r 时传递它的地址。
- 第一次调用: 将
str作为要分割的字符串传入,并传递saveptr的地址。strtok_r会找到第一个非分隔符的字符,str不为NULL,则返回第一个子字符串的起始地址,并将saveptr指向下一个待处理的字符(或字符串结束符\0)。 - 后续调用: 将
str设置为NULL,并再次传递saveptr的地址。strtok_r会从saveptr指向的位置继续查找,直到找到下一个子字符串,然后返回其地址,并更新saveptr指向下一个查找起点。 - 结束调用: 当
strtok_r返回NULL时,表示分割操作完成。
strtok_r 与 strtok 的关键区别
- 线程安全性: 这是最主要的区别。
strtok函数内部使用一个静态变量来保存字符串的分割状态,这意味着,如果多个线程同时调用strtok,它们会竞争修改这个静态变量,导致不可预测的行为和错误。strtok_r通过要求调用者提供saveptr参数来保存状态,避免了对静态变量的依赖,从而实现了线程安全。 - 参数和返回值:
strtok_r的参数和返回值与strtok相似,但strtok_r需要一个额外的saveptr参数来传递状态。 - 可移植性:
strtok_r是 POSIX 标准的一部分,可能在某些非常基础的嵌入式系统或旧版本的 C 库中不可用。strtok则是 ANSI C 标准的一部分,更通用。
使用 strtok_r 的示例

下面是一个简单的示例,演示如何使用 strtok_r 将一个字符串按空格分割:
int main() {
char str[] = "Hello, world! How are you?";
const char delimiters[] = " ,.!?;:[]"; // 定义分隔符集,包括空格和标点
char *token;
char *saveptr;
// 第一次调用,获取第一个 token
token = strtok_r(str, delimiters, &saveptr);
while (token != NULL) {
printf("Token: %s\n", token);
// 继续获取下一个 token,str 必须为 NULL
token = strtok_r(NULL, delimiters, &saveptr);
}
return 0;
}
多线程安全的应用
strtok_r 的主要优势在于其线程安全性,以下是一个简单的多线程示例,展示如何在两个线程中分别分割不同的字符串,而不会相互干扰:
#include <pthread.h>
#include <string.h>
#define DELIMITERS " ,.!?;:[]"
void *thread_function(void *arg) {
char *my_str = (char *)arg;
char *token;
char *saveptr;
printf("Thread started with string: %s\n", my_str);
token = strtok_r(my_str, DELIMITERS, &saveptr);
while (token != NULL) {
printf("Thread: %s\n", token);
token = strtok_r(NULL, DELIMITERS, &saveptr);
}
return NULL;
}
int main() {
pthread_t t1, t2;
char str1[] = "Hello, world!";
char str2[] = "How are you today?";
// 创建线程 1,分割 str1
pthread_create(&t1, NULL, thread_function, (void *)str1);
// 创建线程 2,分割 str2
pthread_create(&t2, NULL, thread_function, (void *)str2);
// 等待线程结束
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}
在这个例子中,两个线程分别处理自己的字符串,并使用各自的 saveptr 变量,由于 strtok_r 是线程安全的,两个线程的分割操作是独立且安全的。
strtok_r 是 C 语言中处理字符串分割任务的一个强大且重要的函数,尤其在需要进行多线程编程时,它通过引入 saveptr 参数来维护状态,有效地解决了 strtok 在多线程环境下不安全的问题,理解 strtok_r 的工作原理和正确使用方法,对于编写健壮、可维护的 C 程序至关重要,在选择使用 strtok 还是 strtok_r 时,应根据程序的多线程需求以及目标平台的库支持来决定。
相关文章:
文章已关闭评论!