C语言中的strtok_r和strtok_s有什么区别?

26

我正在尝试在一个需要能够在Linux和Windows上编译的C程序中使用此函数。起初,我尝试使用strtok_r,但当我在Windows上编译时,它抱怨该函数不存在,并表示它将假定它是一个外部函数,但最后失败了。然后我使用了strtok_s并成功编译了!但是,现在在Linux上尝试时,它抱怨存在“未定义参考‘strtok_s’”。

其中一个是仅适用于Windows的函数,另一个是适用于Linux的函数???我该怎么办才能使其在两个操作系统上都编译通过?


2
strtok_r()是POSIX标准;strtok_s()是Windows标准。在所有实现中(支持C89),请使用可用的普通strtok()函数。 - pmg
4
问题在于我需要在 strtok 一个字符串的同时 strtok 另一个字符串,这就是为什么我需要这个函数,因为它可以保存状态。 - petranaya
3
除非您需要线程或缓冲区溢出安全性,否则请不要使用 @pmg。 - Martin Beckett
1
@MartinBeckett 等等,它保存状态在哪里?只有两个参数,一个是 NULL(在第二个标记上),另一个是你要查找的字符。我想能够strtok一个字符串,在该字符串中查找某些内容并进行strtok,然后返回原始字符串继续进行strtok。 - petranaya
7
这个问题已经很古老了,但我觉得有必要指出的是,strtok_s是C11标准,不仅仅适用于Windows。 - remmy
显示剩余4条评论
6个回答

43

strtok_s 简单来说是 Windows 版本的 strtok_r,而 strtok_r 则是在其他地方都是标准的。

使一个程序在处理像 strtok_s/strtok_r 这样的函数时实现可移植性(跨平台兼容),一种常见的方法就是使用预处理器:

#if defined(_WIN32) || defined(_WIN64)
/* We are on Windows */
# define strtok_r strtok_s
#endif

既然原型和功能是相同的,现在您只需要使用strtok_r


如果在Cygwin上编译,这可能会出现问题,因为它报告自己是Windows,但已经定义了像strtok_r这样的POSIX接口。使用类似#ifndef HAVE_STRTOK_R的东西(并在构建脚本中检测它)会更好。 - R.. GitHub STOP HELPING ICE
6
为了避免与非Unix类代码的标识混淆,Cygwin实际上并未定义_WIN32(或_WIN64)。相反,它定义了__CYGWIN____unix__ - Chris Dodd
3
了解另一个在 C11 中具有完全不同签名的 strtok_s 可能是一个好事情:http://en.cppreference.com/w/c/string/byte/strtok。 - Tor Klingberg
2
@ChrisDodd MinGW仍然存在问题。MinGW预定义了_WIN32,但它支持strtok_r。我认为最好检查_MSC_VER宏。 - Victor

11

这两个函数都是非常丑陋、难以理解的字符串解析习语,通常会以微妙的方式未能满足您特定应用程序的要求。标准C中的纯 strtok 就更是如此。 抛开它们,并编写自己的代码来遍历 char 数组,并根据需要进行分割。使用strchrstrspnstrcspn 可以有所帮助,或者您可以从头开始处理数组。


2
你能再解释一下为什么这些函数不好吗?对我来说,它似乎比逐个字符搜索更直观。 - petranaya
4
(1) 分隔符被覆盖或丢失,所以除非你只有一个可能的分隔符,否则你会失去信息。 (2) 多个连续的分隔符被视为一个,如果你的分隔符是空格,这是有意义的,但如果是逗号等情况,则不是。 - R.. GitHub STOP HELPING ICE
5
请注意,在某些情况下, strtok 的语义可能正是您想要的,唯一的问题是它的状态性(这由strtok_rstrtok_s 解决)。但我发现,每次我尝试使用它们时,它们都至少有一个方面无法支持我的解析需求,而直接编写代码反而更容易。 - R.. GitHub STOP HELPING ICE
9
如果您需要解析已经读入临时缓冲区的字符串,并且希望舍弃分隔符并只保留在解析完成之前有效的字符串,那么strtok正是您想要的函数。而strtok_r/strtok_s则更好一些,因为它是可重入的。 - Chris Dodd

11

由于我没有足够的声望来评论其他答案,所以我必须提供自己的答案。

1)针对这个声明:

"strtok_s是Windows上strtok的缓冲区溢出安全版本。 Windows上的标准strtok是线程安全的..."

这是不正确的。在MSVC编译器中,strtok_s是线程安全的版本。 strtok不是线程安全的!

2)针对这个声明:

"如果在报告自己为Windows但已经定义了POSIX接口(如strtok_r)的Cygwin上编译,这可能会破坏它。"

同样不正确。区别在于您使用哪个编译器。当使用微软的Visual C++编译器(MSVC)时,函数为strtok_s。另一个编译器,例如GNU编译器集合(GCC),可能使用不同的标准库实现,如strtok_r。在确定要使用哪个函数时,请考虑编译器而不是目标平台。

在我看来,Joachim Pileborg的答案是此页面上最好的答案。不过,它需要进行小的编辑:

#if defined(_WIN32) /* || defined(_WIN64) */
#define strtok_r strtok_s
#endif

_WIN32和_WIN64是由MSVC编译器提供的预定义宏。在编译64位目标时,定义了_WIN64。_WIN32对于32位和64位目标都定义了。这是Microsoft为了向后兼容而做出的妥协。_WIN32被创建来指定Win32 API。现在,你应该考虑使用_WIN32来指定Windows API——它不仅适用于32位目标。


3
关于第一点,你是错误的:在Windows系统上,strtok 线程安全的,即使strtok有其他问题,如@JameyKirby在另一个答案中所解释的那样,两个不同的线程可以在同一个进程中安全地使用该函数。 - AntoineL
MinGW 预定义了 _WIN32,但它支持 strtok_r,因此最好检查 _MSC_VER 宏。 - Victor

7

仅作澄清。在Windows中,strtok是线程安全的。strtok使用TLS变量来维护每个线程的最后一个指针。但是,您不能使用strtok交错访问超过一个标记字符串。 strtok_rstrtok_s都通过允许用户通过第三个参数来维护上下文来解决这个交错问题。希望这有所帮助。


虽然对于正在寻找便携式解决方案的 OP 没有帮助,但这对我来说非常有帮助,让我知道为什么我的旧代码在 Linux 上崩溃而在 Windows 上却没有。因为我认为自己知道 strtok 的行为,所以我没有仔细阅读 Windows strtok 文档... - Matthias

5

strtok_r是POSIX系统上的一个线程安全的strtok版本。

strtok_s是Windows上一个缓冲区溢出安全的strtok版本。Windows上的标准strtok是线程安全的,所以strtok_s也应该是。


strtok_r 是 POSIX 的一部分,已经存在了多年(几十年?) - R.. GitHub STOP HELPING ICE
我仍然认为答案不是非常有用,因为它只涵盖了 OP(大多数情况下)已经知道的内容,而没有解决问题的方法。 - R.. GitHub STOP HELPING ICE
@R - 解决方案是您可以在Posix上使用strok_r,在Windows上使用strtok_s,Joachim的解决方案描述了如何操作。或者您可以仅使用Posix子系统编写Windows程序。 - Martin Beckett
@MartinBeckett 这是C11 Annex K标准,不仅局限于Windows :) - Alexander Riccio
2
@AlexanderRiccio Microsoft的strtok_s和C11的strtok_s是完全不同的! Microsoft的strtok_s只有3个参数,而C11的strtok_s有4个参数。 Microsoft的strtok_s的原型是char* strtok_s(char* str, const char* delimiters, char** context);,而C11的strtok_s的原型是char *strtok_s(char *restrict str, rsize_t *restrict strmax, const char *restrict delim, char **restrict ptr); - Victor

1

MinGW也预定义了_WIN32,但它支持strtok_r,因此我认为检查_WIN32宏不是一个好主意。最好检查_MSC_VER宏,这是Microsoft Visual Studio的宏。

#ifdef _MSC_VER
#define strtok_r strtok_s
#endif

警告:Microsoft的strtok_s和C11的strtok_s完全不同!Microsoft的strtok_s只有3个参数,而C11的strtok_s有4个参数,因此这可能会在未来产生兼容性问题。
Microsoft的strtok_s原型是:
char* strtok_s(char* str, const char* delimiters, char** context);

C11的strtok_s的原型是:
char *strtok_s(char *restrict str, rsize_t *restrict strmax, const char *restrict delim, char **restrict ptr);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接