strtok坏了吗?或者只是棘手?

时间:2015-02-18 16:07:50

标签: c std strtok

strtok无可救药地被打破了吗?

关于C中文本解析的许多StackOverflow问题,有人建议使用strtok, 一个共同的答复是strtok永远不应该被使用,它是无可救药地被打破的。

有些海报声称strtok的问题仅限于多线程问题,并且在单线程环境中是安全的。

什么是正确的答案?
它有用吗?
是否无可救药地被打破了? 你能用例子来回答你的答案吗?

1 个答案:

答案 0 :(得分:2)

是的,即使在一个简单的单线程程序 中,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