【问题标题】:Is strtok broken? Or just tricky?strtok坏了吗?还是只是棘手?
【发布时间】:2015-02-18 16:07:50
【问题描述】:

strtok 彻底崩溃了吗?

关于 C 中文本解析的许多 StackOverflow 问题,有人会建议使用 strtok, 一个常见的回答是 strtok 永远不应该被使用,它已经被彻底破坏了。

有些发帖者声称strtok的问题仅限于多线程问题,在单线程环境下是安全的。

正确答案是什么?
它有效吗?
是不是彻底坏了?
你能用例子来支持你的答案吗?

【问题讨论】:

  • 它没有“损坏”。它具有明确定义的行为。在某些情况下,这种行为会导致问题,但这些情况并不是该功能的设计目的。更准确的说法是,strtok经常用错。
  • @Sander:见下面的例子。每一段代码都正确且安全地编写,strtok 仍然会导致未定义行为。
  • 不,您的代码无效。您将strtok 视为可重入。但事实并非如此。
  • 随后对同一字符串的strtok 调用通常被视为对strtok 的一次调用被中断并稍后重新输入(因为两者在概念上是相同的) .但是,如果您愿意,我会以不同的方式表达我之前的评论:strtok 不可重入(它保持全局状态)的相同原因是您的代码无效的原因。
  • 我的意思是,声称strtok 已损坏,因为您无法以从未设计过的方式使用它,这是没有意义的。当然,strtok 只能在某些明确定义的条件下安全使用(这就是他们后来添加strtok_s 的原因)。但是在那些定义明确的条件下,strtok 擅长它的工作,并且没有被破坏。

标签: c std strtok


【解决方案1】:

是的,strtok 被彻底破坏了,即使在一个简单的单线程程序中,我将通过一些示例代码来演示这种失败:

让我们从一个简单的文本分析器函数开始,使用strtok 收集有关文本句子的统计信息。 此代码将导致未定义的行为。

在此示例中,句子是由空格、逗号、分号和句点分隔的一组单词。

// Example:
//     int words, longest;
//     GetSentenceStats("There were a king with a large jaw and a queen with a plain face, on the throne of England.", &words, &longest);
// will report there are 20 words, and the longest word has 7 characters ("England").
void GetSentenceStats(const char* sentence, int* pWordCount, int* pMaxWordLen)
{
    char* delims = " ,;.";           // In a sentence, words are separated by spaces, commas, semi-colons or period.
    char* input = strdup(sentence);  // Make an local copy of the sentence, to be modified without affecting the caller.

    *pWordCount = 0;                 // Initialize the output to Zero
    *pMaxWordLen = 0;

    char* word = strtok(input, delims);
    while(word)
    {
        (*pWordCount)++;
        *pMaxWordLen = MAX(*pMaxWordLen, (int)strlen(word));
        word = strtok(NULL, delims);
    }
    free(input);
}

这个简单的功能有效。到目前为止没有错误。


现在让我们扩充我们的库,添加一个收集文本段落统计信息的函数。
段落是由感叹号、问号和句号分隔的一组句子。

它将返回段落中的句子数,以及最长句子中的单词数。
也许最重要的是,它将使用早期的函数GetSentenceStats 来帮助

void GetParagraphStats(const char* paragraph, int* pSentenceCount, int* pMaxWords)
{
    char* delims = ".!?";             // Sentences in a paragraph are separated by Period, Question-Mark, and Exclamation.
    char* input = strdup(paragraph);  // Make an local copy of the paragraph, to be modified without affecting the caller.

    *pSentenceCount = 0;
    *pMaxWords = 0;
    char* sentence = strtok(input, delims);
    while(sentence)
    {
        (*pSentenceCount)++;

        int wordCount;
        int longestWord;
        GetSentenceStats(sentence, &wordCount, &longestWord);
        *pMaxWords = MAX(*pMaxWords, wordCount);
        sentence = strtok(NULL, delims);    // This line returns garbage data, 
    }
    free(input);
}

这个函数看起来也非常简单明了。
但它不起作用,正如这个示例程序所展示的那样。

int main(void)
{
    int cnt;
    int len;

    // First demonstrate that the SentenceStats function works properly:
    char *sentence = "There were a king with a large jaw and a queen with a plain face, on the throne of England."; 
    GetSentenceStats(sentence, &cnt, &len);
    printf("Word Count: %d\nLongest Word: %d\n", cnt, len);
    // Correct Answer:
    // Word Count: 20
    // Longest Word: 7   ("England")


    printf("\n\nAt this point, expected output is 20/7.\nEverything is working fine\n\n");

    char paragraph[] =  "It was the best of times!"   // Literary purists will note I have changed Dicken's original text to make a better example
                        "It was the worst of times?"
                        "It was the age of wisdom."
                        "It was the age of foolishness."
                        "We were all going direct to Heaven!";
    int sentenceCount;
    int maxWords;
    GetParagraphStats(paragraph, &sentenceCount, &maxWords);
    printf("Sentence Count: %d\nLongest Sentence: %d\n", sentenceCount, maxWords);
    // Correct Answer:
    // Sentence Count: 5
    // Longest Sentence: 7  ("We were all going direct to Heaven")

    printf("\n\nAt the end, expected output is 5/7.\nBut Actual Output is Undefined Behavior! Strtok is hopelessly broken\n");
    _getch();
    return 0;
}

strtok 的所有调用都是完全正确的,并且是在单独的数据上。
但结果是未定义的行为!

为什么会这样?
GetParagraphStats 被调用时,它开始一个strtok 循环来获取句子。 在第一句话中,它将调用GetSentenceStatsGetSentenceStats 也将是一个strtok 循环,丢失由GetParagraphStats 建立的所有状态。 当GetSentenceStats返回时,调用者(GetParagraphStats)会再次调用strtok(NULL)获取下一句。

strtok认为这是一个继续前一个操作的调用,并将继续标记现在已释放的内存! 结果是可怕的未定义行为。

什么时候可以安全地使用 strtok?
即使在单线程环境中strtok 也只能使用当程序员/架构师确定两个条件时安全:

  • 使用strtok 的函数绝不能调用任何也可能使用strtok 的函数。
    如果它调用也使用strtok 的子例程,它自己对strtok 的使用可能会被中断。

  • 使用strtok 的函数绝不能被任何也使用strtok 的函数调用。
    如果这个函数曾经被另一个使用strtok 的例程调用,那么这个函数将中断调用者对strtok 的使用。

在多线程环境下,使用strtok 更是不可能,因为程序员需要确保在当前线程上只有一个strtok 使用,并且没有其他线程在使用strtok 要么。

【讨论】:

  • @sashoalm AFAIK 不是全局的,它是函数中的静态变量。
  • 这就是为什么我们有strtok_s(),这样您就可以同时处理不同的字符串。
  • 为什么会被否决?有什么不对吗?对我来说看起来很合理,(不像 strtok 静态 - 呃!)。
  • @WeatherVane:在 linux 上,您需要使用 strtok_r,它在 Posix 中但在 C11 中没有。 strtok_s 在 C11 的可选附件 K 中,glibc 目前没有实现。所以事情没那么简单。
  • 整个讨论似乎带有宗教战争的感觉。任何反对您的想法的技术相关声明(例如,here,以及上面@Sander De Dycker 提供的论点,)都已得到满足您对strtok() 实施的意见。如果你对实施有如此强烈的感受,你能不能不把它提交给C standards committee
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-21
  • 2020-07-26
  • 1970-01-01
  • 2022-09-30
  • 2018-12-22
  • 1970-01-01
相关资源
最近更新 更多