strtok
是 C 语言库中的一个函数,用于在字符串上执行分词操作。这意味着它可以用于将字符串分解成多个标记或段,这些标记之间由指定的分隔符分隔。
以下是 strtok
函数的原型:
char *strtok(char *str, const char *delim);
strtok
在 str
中发现这些字符时,它会认为这是一个分隔符,并据此分解字符串。不可重入: strtok
在内部使用静态缓冲区来保存当前的位置,这意味着它不是线程安全的和不可重入的。对于多线程应用程序或需要同时分解多个字符串的应用程序,推荐使用 strtok_r
函数(如果可用)。
修改原字符串: strtok
会在找到的分隔符位置放置 ‘\0’ 字符,这样会修改输入字符串。
#include
#include
int main() {
char string[50] = "Hello,world,this,is,a,test";
char *token = strtok(string, ","); // 使用逗号作为分隔符
while(token != NULL) {
printf("%s\n", token);
token = strtok(NULL, ",");
}
return 0;
}
输出:
Hello
world
this
is
a
test
在此示例中,字符串 "Hello,world,this,is,a,test"
被逗号分隔,并且每个标记都被单独打印出来。
strtok_r
是一个分词函数,与 strtok
功能类似,但它是线程安全的和可重入的。这得益于其额外的参数,该参数用于保存函数的内部状态,而不是像 strtok
那样使用静态缓冲区。
char *strtok_r(char *str, const char *delim, char **saveptr);
strtok_r
在 str
中发现这些字符时,它会认为这是一个分隔符,并据此分解字符串。与 strtok
相比,strtok_r
的优势在于它不会修改全局或静态变量,因此它在多线程环境中是安全的。
#include
#include
int main() {
char string[50] = "Hello,world,this,is,a,test";
char *token;
char *saveptr; // 用于保存 strtok_r 的内部状态
token = strtok_r(string, ",", &saveptr);
while(token != NULL) {
printf("%s\n", token);
token = strtok_r(NULL, ",", &saveptr);
}
return 0;
}
输出:
Hello
world
this
is
a
test
在此示例中,字符串 "Hello,world,this,is,a,test"
被逗号分隔,并且每个标记都被单独打印出来,就像使用 strtok
函数那样。但由于使用了 strtok_r
和 saveptr
,此代码在多线程环境中也是安全的。
使用 strtok
在多线程环境中不是线程安全的。下面我们来看个例子。
在这个例子中,尝试在两个线程中使用 strtok
来分词两个字符串。但请注意,由于 strtok
使用静态内部存储来保持其状态,这可能会导致不可预测的行为:
#include
#include
#include
#include
void* tokenizeString(void* arg) {
char* data = (char*) arg;
char* token;
token = strtok(data, ",");
while (token != NULL) {
printf("Thread %ld: %s\n", pthread_self(), token);
token = strtok(NULL, ",");
}
pthread_exit(NULL);
}
int main() {
pthread_t threads[2];
char threadStrings[2][50] = {
"Hello,world,this,is,thread,one",
"Another,string,for,thread,two"
};
for(int t = 0; t < 2; t++) {
int rc = pthread_create(&threads[t], NULL, tokenizeString, threadStrings[t]);
if (rc) {
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
// Wait for all threads to complete
for(int t = 0; t < 2; t++) {
pthread_join(threads[t], NULL);
}
pthread_exit(NULL);
return 0;
}
由于 strtok
的静态存储特性,当多个线程尝试访问它时,其中一个线程可能会“接管”另一个线程的分词过程。这可能导致某些标记被忽略或重复,或者出现其他不可预测的行为。
程序运行结果如下:
Thread 139977209935424: Hello
Thread 139977209935424: string
Thread 139977209935424: for
Thread 139977209935424: thread
Thread 139977209935424: two
Thread 139977201542720: Another
建议:在多线程环境中,应该避免使用 strtok
,而是使用 strtok_r
或其他线程安全的分词方法。
下面是一个在多线程环境中使用 strtok_r
对两个字符串进行分词的示例。
我们创建两个线程,每个线程处理一个字符串,并使用 strtok_r
对其进行分词。
#include
#include
#include
#include
typedef struct {
char string[50];
const char *delimiter;
} ThreadData;
void* tokenizeString(void* arg) {
ThreadData* data = (ThreadData*) arg;
char *token;
char *saveptr;
token = strtok_r(data->string, data->delimiter, &saveptr);
while (token != NULL) {
printf("Thread %ld: %s\n", pthread_self(), token);
token = strtok_r(NULL, data->delimiter, &saveptr);
}
pthread_exit(NULL);
}
int main() {
pthread_t threads[2];
ThreadData threadData[2] = {
{"Hello,world,this,is,thread,one", ","},
{"Thread-two:splitting|by|pipes", "|"}
};
for(int t = 0; t < 2; t++) {
int rc = pthread_create(&threads[t], NULL, tokenizeString, &threadData[t]);
if (rc) {
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
// Wait for all threads to complete
for(int t = 0; t < 2; t++) {
pthread_join(threads[t], NULL);
}
pthread_exit(NULL);
return 0;
}
在本例中,我们为每个线程定义了一个 ThreadData
结构,该结构包含要分词的字符串和相应的分隔符。我们在多线程环境中安全地使用了 strtok_r
,因为它不依赖于全局或静态变量来保存其状态。
程序运行结果如下:
Thread 140242684208704: Thread-two:splitting
Thread 140242684208704: by
Thread 140242684208704: pipes
Thread 140242692601408: Hello
Thread 140242692601408: world
Thread 140242692601408: this
Thread 140242692601408: is
Thread 140242692601408: thread
Thread 140242692601408: one