C strtok_s 函数
最后修改:2025 年 4 月 8 日
字符串标记(tokenization)是 C 语言编程中的一项常见操作,而 `strtok_s` 函数是用于分割字符串的 `strtok` 函数的更安全版本。本教程将深入探讨 `strtok_s`,包括其语法、用法以及相对于 `strtok` 的优势。我们将通过实际示例进行探讨,并讨论为何在现代 C 编程中应优先使用 `strtok_s`。理解 `strtok_s` 有助于编写更安全、更可靠的字符串处理代码。
什么是 strtok_s?
`strtok_s` 函数是用于将字符串分割成标记(tokens)的 `strtok` 函数的一个更安全替代方案。它是 C11 标准的 Annex K 边界检查接口的一部分。`strtok_s` 添加了一个上下文参数(context parameter)以在调用之间维护状态,使其成为线程安全的。它还执行运行时约束检查。与 `strtok` 不同,它可以检测无效参数和缓冲区溢出。在安全性至关重要的新代码中,请始终优先使用 `strtok_s`。
strtok_s 的基本用法
本示例演示了使用 `strtok_s` 进行基本的字符串标记。
#define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> #include <string.h> int main() { char str[] = "apple,banana,cherry"; char *token; char *context = NULL; const char *delim = ","; // First call to strtok_s token = strtok_s(str, delim, &context); while (token != NULL) { printf("Token: %s\n", token); // Subsequent calls with NULL as first argument token = strtok_s(NULL, delim, &context); } return 0; }
此代码将逗号分隔的字符串分割成单独的标记。`strtok_s` 将其状态保存在 `context` 指针中。第一次调用使用要标记的字符串,后续调用使用 NULL。每次调用都会返回指向下一个标记的指针,或在完成后返回 NULL。与 `strtok` 相比,上下文参数使其成为线程安全的。
使用多个分隔符进行标记
`strtok_s` 可以处理多个分隔符字符,如下所示。
#define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> #include <string.h> int main() { char str[] = "apple;banana cherry,orange"; char *token; char *context = NULL; const char *delim = "; ,"; token = strtok_s(str, delim, &context); while (token != NULL) { printf("Fruit: %s\n", token); token = strtok_s(NULL, delim, &context); } return 0; }
本示例使用多个分隔符(分号、空格和逗号)对字符串进行标记。`strtok_s` 将这些字符的任何序列视为单个分隔符。输出显示所有水果都已分隔,无论使用了哪种分隔符。这种灵活性使 `strtok_s` 对于解析复杂输入非常有用。始终确保您的分隔符字符串包含所有可能的分隔符。
处理空标记
本示例演示了 `strtok_s` 如何处理连续的分隔符。
#define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> #include <string.h> int main() { char str[] = "apple,,banana,,,cherry"; char *token; char *context = NULL; const char *delim = ","; int count = 0; token = strtok_s(str, delim, &context); while (token != NULL) { printf("Token %d: '%s'\n", ++count, token); token = strtok_s(NULL, delim, &context); } printf("Total tokens: %d\n", count); return 0; }
当出现连续分隔符时,`strtok_s` 会跳过它们之间的空标记。此示例中有多个连续的逗号,但只找到三个非空标记。如果您需要保留空标记,请考虑使用 `strsep` 或手动解析等替代方法。上下文参数即使在复杂的分隔符模式下也能确保正确的状态跟踪。
在嵌套循环中进行标记
本示例展示了如何在嵌套标记中使用 `strtok_s`。
#define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> #include <string.h> int main() { char data[] = "name=John;age=30;city=New York"; char *outer_token; char *outer_context = NULL; char *inner_token; char *inner_context = NULL; const char *outer_delim = ";"; const char *inner_delim = "="; outer_token = strtok_s(data, outer_delim, &outer_context); while (outer_token != NULL) { printf("Pair: %s\n", outer_token); inner_token = strtok_s(outer_token, inner_delim, &inner_context); printf(" Key: %s\n", inner_token); inner_token = strtok_s(NULL, inner_delim, &inner_context); printf(" Value: %s\n", inner_token); outer_token = strtok_s(NULL, outer_delim, &outer_context); } return 0; }
此代码首先按分号分割,然后按等号分割,演示了嵌套标记。每个级别都使用自己的上下文变量,从而实现了安全的嵌套操作。外部循环分割键值对,而内部循环则分离键和值。这种模式在配置文件解析中很常见。独立的上下文变量可防止标记级别之间的干扰。
使用 strtok_s 进行错误处理
本示例展示了如何对 `strtok_s` 进行适当的错误处理。
#define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> #include <string.h> #include <errno.h> int main() { char *str = NULL; // Invalid input char *token; char *context = NULL; const char *delim = ","; token = strtok_s(str, delim, &context); if (token == NULL && errno != 0) { perror("strtok_s failed"); return 1; } while (token != NULL) { printf("Token: %s\n", token); token = strtok_s(NULL, delim, &context); } return 0; }
`strtok_s` 在遇到无效参数时会设置 `errno`。本示例演示了在调用失败后检查错误。当传入 NULL 字符串指针时,`strtok_s` 返回 NULL 并将 `errno` 设置为 EINVAL。为了实现健壮的错误处理,请务必在每次调用后同时检查返回值和 `errno`。这是其相对于 `strtok` 的一个关键优势,因为 `strtok` 提供了任何错误报告机制。
使用 strtok_s 的最佳实践
- 始终使用独立的上下文变量: 用于嵌套或并行标记。
- 检查错误: 每次调用后验证返回值和 errno。
- 优先于 strtok: 在所有新代码中都使用 strtok_s 以实现线程安全。
- 在标记过程中不要修改字符串: 这可能导致未定义的行为。
- 妥善处理空标记: 决定是需要检测它们还是跳过它们。
来源
本教程涵盖了 `strtok_s` 函数,从基本用法到高级场景。作为 `strtok` 的更安全替代方案,它应成为您在现代 C 编程中进行字符串标记的首选。
作者
列表 C 标准库。