3
int i = 0;
while(fgets(lineStr, sizeof(lineStr), pFile)!=NULL){
    puts(lineStr);
    pch = strtok (lineStr, delim);
    while(pch != NULL){
        printf("%s\n",pch);
        pch = strtok(NULL,delim);
    }
}

概述:我正在尝试编写 grep 的微型版本(也就是在文本文件中查找单词的出现次数)。整个代码http://pastebin.com/VzTJkLK3

问题:我正在尝试使用 strtok 来标记表示一行文本的字符数组。我注意到使用 gdb 时出现分段错误,例如

程序收到信号 SIGSEGV,分段错误。__strlen_sse2 () at ../sysdeps/x86_64/multiarch/../strlen.S:31 31 ../sysdeps/x86_64/multiarch/../strlen.S:没有这样的文件或目录。

任何提示或指向更多文档的链接表示赞赏。

PS:有人告诉我,使用 strtok 不是一个好的编程习惯——顺便说一句,我是 C 的菜鸟。你会推荐什么替代方案?

4

1 回答 1

11

您的代码不包括包含和string.h的原型。由此产生的行为是为遗留 C 编译提供的一个有趣的“功能”;隐式声明。strlen()strtok()

在 C 语言中,如果您在翻译单元中使用它之前未声明正确的原型(或实际函数未实现) ,编译器将尽职尽责地为您生成一个,默认返回值类型为int. 这通常是一个大问题,任何值得称道的体面的编译器至少会给你一个警告,类似于“警告函数“foo”返回的隐式声明int

那么为什么会如此糟糕呢?好吧,如果不包括string.h,编译器会假定您正在使用的两个函数strlen()strtok(),看起来像这样:

int strlen();
int strtok();

这声明了两个函数原型,都返回int和接受零个或多个参数。C 调用此类函数的另一个“有用”特性是允许您将任何您想要的东西作为参数传递给这些函数。编译器会很乐意将它们按值推送到堆栈中:

int n = strlen(str); // pushes char* on the stack, then makes the call.

和类似,但不完全相同:

char *p = strtok(str, delim); // pushes two char* on the stack

那么,为什么strlen看起来有效,但却strtok出错了呢?好吧,因为在您的平台上,int(您的未声明函数的隐含返回类型)与您存储所述返回值的地方strtok()的字节大小不同。char*您很可能在 64 位平台上并且int是 32 位的,但指针是 64 位的。

因此,只有一半的指针被保存,另一半(32 位)不被保留。因此返回的指针是无效的,因此是kerboom。

原因strlen似乎只是因为返回的值int“适合”到您的结果变量中。即函数实际上返回(在其return语句中)一个 64 位 int,但调用方(您的代码)只保存了“底部”一半。下半部分的值足以准确反映长度(上半部分为0)。如果字符串很大并且需要超过 32 位来表示它的长度,就会出现同样的问题。(请注意,这一点您还会遇到其他问题,例如如何将连续的 4gB 字符串放入进程地址空间)。

注意:与此密切相关的是您从未在 C 程序中转换结果的主要原因。硬演员隐藏了将从这里发出的警告。这也是最好的证据,最好始终启用迂腐警告级别并打开警告作为错误。这样做的话,这样的事情不会通过编译并且会很快被发现。malloc()

于 2013-10-10T22:18:22.110 回答