为什么strtok()被认为是不安全的?

36

strtok中哪些特性是不安全的(涉及缓冲区溢出),我需要注意什么?

对我来说有点奇怪的是,Visual C++中“安全”的strtok_s有一个额外的“context”参数,但在其他方面它看起来是相同的... 它是相同的吗,还是实际上有所不同?


1
也许是因为strtok的两个参数都是char指针,所以strtok可能无法到达任何终止字符并溢出任何本地缓冲区? - Heisenbug
@0verbose:嗯……但这不是所有 C 字符串都是这样吗? - user541686
3
顺便提一下,既然有strsep,为什么还要使用这个? - Mel
请查看[此问题](http://stackoverflow.com/questions/28588170/is-strtok-broken-or-just-tricky/28588171)上有关为什么strtok不好的详细信息。 - abelenky
显示剩余3条评论
4个回答

30
根据 此文档 的 strtok_s 部分内容:

6.7.3.1 strtok_s 函数解决了 strtok 函数中的两个问题:

  1. 新增参数 s1max,防止 strtok_s 在令牌化的字符串外部存储。(被划分为标记的字符串既是函数的输入又是输出,因为 strtok_s 将空字符存储到该字符串中。)
  2. 新增参数 ptr,消除了静态内部状态,防止 strtok 不可重入(子条款 1.1.12)。 (ISO/IEC 9899 函数 wcstok 和 ISO/IEC 9945(POSIX)函数 strtok_r 以相同方式解决此问题。)

1
+1 我之前没有看过那个文档,看起来很清楚地解释了答案。 :) - user541686
5
请注意,strtok_s() 的此规范来自 ISO/IEC 9899:2011 的(可选)K附录,并且其定义与 Microsoft 的 strtok_s() 规范不同。 - Jonathan Leffler

13

这并不会有任何安全隐患。您只需要了解它的工作原理以及如何使用它。在编写代码和单元测试之后,只需要额外几分钟时间就可以使用 valgrind 重新运行单元测试,以确保您在内存边界内操作。man 手册已经说明了一切:

漏洞

在使用这些函数时要小心。如果您确实要使用它们,请注意以下事项:

  • 这些函数会修改它们的第一个参数。
  • 这些函数不能用于常量字符串。
  • 定界字符的标识丢失了。
  • strtok() 函数在解析时使用静态缓冲区,因此不是线程安全的。如果这很重要,请使用 strtok_r()

我对 strtok_s 进行了一些小的编辑... 你知道它和普通的 strtok 有什么不同吗? - user541686
我不熟悉那个,但它听起来像是strtok_r。如果您要同时对两个或更多字符串进行标记化,则需要其中之一。如果您只处理一个字符串,则无需使用它。 - Bob
更明确地说,strtok会在你的字符串中保留一个隐藏的静态引用。如果你用多个字符串调用它,引用将不正确。strtok_r/s 强制你提供给函数的引用。 - Bob
8
可能并不是 在另一个线程中运行 strtok。这可能是由你的插件引起的。或者你的代码被用于在另一个线程中运行 strtok 的库中,而主应用程序也使用了该库。或者某天你决定将你的代码改为多线程,在那之后你已经忘记了其中有一个 strtok 调用。 - Gabe
请删除那句话:“这没有任何不安全的地方。”你刚才引用了手册中指出它不是线程安全的部分。 - Wyck

8

strtok在Visual C++中是安全的(但其他地方不是),因为它使用线程本地存储来保存其在调用之间的状态。在其他地方,全局变量用于保存strtok()的状态。

然而,即使在VC++中,strtok也仍然有点奇怪 - 你不能在同一线程中同时对不同的字符串使用strtok()。例如,以下代码将无法正常工作:

     token = strtok( string, seps );
     while(token)
     {
        printf("token=%s\n", token)
        token2 = strtok(string2, seps);
        while(token2)  
        {
            printf("token2=%s", token2);
            token2 = strtok( NULL, seps );
        }
        token = strtok( NULL, seps );
     }

为什么 strtok 不适用 - 因为每个线程只能在线程本地存储中保存单个状态,而这里需要两个状态 - 一个是第一个字符串的状态,另一个是第二个字符串的状态。因此,尽管 VC++ 中的 strtok 是线程安全的,但它不是可重入的。

strtok_s(或其他任何地方的 strtok_r)提供了一个显式状态,因此 strtok 变得可重入。


0
如果您没有一个正确的空终止字符串,那么您将会遇到缓冲区溢出的问题。另外请注意(这是我吃过亏才知道的),strtok似乎不关心内部字符串。例如,有"hello"/"world"将解析为"hello"/"world",而"hello/world"将解析为"hello world"。请注意,它在/处分割并忽略了它在括号内的事实。

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