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

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

在关于C语言解析的许多StackOverflow问题中,有人会建议使用strtok ,并且一个常见的答案是不应该使用strtok ,它是绝对无法破解的。

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

什么是正确的答案?
它有用吗?
它无可救药地被打破了吗?
你能举例说明你的答案吗?

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

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


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

它将返回段落中的句子数量和最长句子中的单词数量。
也许最重要的是,它将使用早期的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); } 

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

 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 -loop来获取句子。 在第一句话,它将调用GetSentenceStatsGetSentenceStats也将成为一个strtok -loop,失去GetParagraphStats建立的所有州。 当GetSentenceStats返回时,调用者( GetParagraphStats )将再次调用strtok(NULL)来获取下一个句子。

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

什么时候使用strtok是安全的?
即使在单线程环境中只有在程序员/架构师确定两个条件时才能安全地使用strtok

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

  • 任何使用strtok的函数都不能调用使用strtok的函数。
    如果此函数曾被另一个使用strtok的例程调用,则此函数将中断调用者对strtok的使用。

在multithreading环境中,使用strtok更加不可能,因为程序员需要确保当前线程上只有一次使用strtok ,而且其他线程也没有使用strtok