从strtok()返回的指针为什么在传递给printf()时会导致分段错误?

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时,会出现段错误,如下所示:

Program received signal SIGSEGV, Segmentation fault. __strlen_sse2 () at ../sysdeps/x86_64/multiarch/../strlen.S:31 31 ../sysdeps/x86_64/multiarch/../strlen.S: No such file or directory.

欢迎提供任何提示或更多文档链接。
附注:有人告诉我,使用strtok不是“好”的编程实践 - 我是C语言的新手。你能推荐一些替代方法吗?

1
strtok存在的问题是每个线程一次只能执行一个循环。推荐的替代方法是使用strtok_r,它使用一个额外的参数而不是“全局”变量,但它不在C标准中(我认为它包含在POSIX标准中,所以只要你在nx上使用它就没有问题)。 - Medinoc
我确实在pastebin上看到了一个问题,但它会在循环之前失败:你的代码在检查argc之前就打开了argv[2]中的文件... - Medinoc
1
你的示例中没有包含<string.h>头文件,这是strtok()(以及strlen())声明的位置。如果此代码与您的相同,则需要#include <string.h>,如果它可以正常工作,我会在您报告后解释原因。另外,请选择一种语言。这看起来根本不像C++代码,如果不打算使用C++,则应该删除该语言标签。 - WhozCraig
包含string.h库解决了它。谢谢@WhozCraig - 我需要新眼镜 :) - user228137
2
我想这个故事的寓意是不要忽略编译器警告。 - Charlie Burns
显示剩余5条评论
1个回答

11

您的代码没有包含string.h,无法包含strlen()strtok()的原型。这将导致一个有趣的“特性”,为了兼容C编译而提供的隐式声明

在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 位)不被保留。因此,返回的指针无效,导致错误。 strlen 看起来有效的原因仅仅是因为作为 int 返回的值“适合”于您的结果变量中。也就是说,函数实际上返回了一个 64 位 int,在调用方(您的代码)中只保存了“底部”的一半。底部的值足以准确反映长度(顶部是 0)。如果字符串巨大并且需要超过 32 位来表示其长度,则会出现同样的问题。(请注意,在这种情况下,您将面临其他问题,例如如何将连续的 4gB 字符串放入进程地址空间中)。
注意:与此密切相关的是您在 C 程序中永远不要malloc() 的结果进行强制转换的主要原因。硬转换会隐藏将发出的警告。最好的做法是始终启用严格的警告级别并打开错误警告。这样,像这样的事情就不会通过编译,并且很快就会被发现。

1
优秀的答案。请注意编译器的警告。 - Charlie Burns
对于这个出色的答案点赞。当我在我的系统上测试他的代码时,没有segfault,真的很神秘。现在我明白了 - 我的平台是32位的。 - Filipe Gonçalves

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