【问题标题】:Why does a pointer returned from strtok() cause a segmentation fault when passed to printf()?为什么 strtok() 返回的指针在传递给 printf() 时会导致分段错误?
【发布时间】:2013-10-18 20:44:33
【问题描述】:
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 () 在 ../sysdeps/x86_64/multiarch/../strlen.S:31 31 ../sysdeps/x86_64/multiarch/../strlen.S: 没有这样的文件或目录。

感谢任何指向更多文档的提示或链接。

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

【问题讨论】:

  • strtok 的问题是每个线程一次只能执行一个循环。推荐的替代方案是strtok_r,它使用附加参数而不是“全局”变量,但它不在 C 标准中(我认为它包含在 POSIX 标准中,只要你在 n x 你可以毫无问题地使用它)。
  • 只要lineStr 是一个有效的缓冲区,delim 是一个有效的字符串,pFile 是一个有效的输入流,我在您的代码示例中看不到任何可能导致段错误的内容。
  • lineString 是在第 7 行初始化的 256 字节数组 pastebin.com/VzTJkLK3
  • 您的样本不包括 <string.h>,这是声明 strtok() 的位置。 (以及strlen())。如果此代码与您的相同,您需要#include <string.h>,如果它可以正常工作,我会在您报告后解释原因。另外,选择一种语言。这一点看起来不像 C++ 代码,如果不是这样,则应该删除该语言标记。
  • 我猜这个故事的寓意是不要忽视编译器警告。

标签: c string pointers segmentation-fault


【解决方案1】:

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

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

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

int strlen();
int strtok();

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

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

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

【讨论】:

  • 优秀的答案。注意来自编译器的警告。
  • +1 很好的答案。当我在我的系统上测试他的代码时,没有段错误,这真的很神秘。现在我明白了——我的平台是 32 位的。
猜你喜欢
  • 2020-04-15
  • 1970-01-01
  • 2021-08-14
  • 1970-01-01
  • 2014-10-15
  • 1970-01-01
  • 2021-11-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多