为啥这段代码在 64 位架构上会出现段错误,但在 32 位上却能正常工作?

Posted

技术标签:

【中文标题】为啥这段代码在 64 位架构上会出现段错误,但在 32 位上却能正常工作?【英文标题】:Why does this code segfault on 64-bit architecture but work fine on 32-bit?为什么这段代码在 64 位架构上会出现段错误,但在 32 位上却能正常工作? 【发布时间】:2011-11-24 14:42:49 【问题描述】:

我遇到了以下 C 难题:

问:为什么以下程序在 IA-64 上会出现段错误,但在 IA-32 上可以正常工作?

  int main()
  
      int* p;
      p = (int*)malloc(sizeof(int));
      *p = 10;
      return 0;
  

我知道int 在 64 位机器上的大小可能与指针的大小不同(int 可能是 32 位,指针可能是 64 位)。但我不确定这与上述程序有何关系。 有什么想法吗?

【问题讨论】:

不包括 stdlib.h 这样的傻事吗? 这段代码在我的 64 位机器上运行良好。如果您 #include stdlib.h(对于 malloc),它甚至可以在没有警告的情况下编译 天啊! @user786653 指出了重要的一点。使用#include <stdlib.h>,可以完美找到,但这不是问题所在。 @delnan - 它不必像那样工作,它可能在sizeof(int) == sizeof(int*) 的平台上合法地失败,例如,如果指针通过不同的寄存器返回到ints in使用的调用约定。 在 C99 环境中,编译器应该至少给你一个关于 malloc() 隐式声明的警告。 GCC 说:warning: incompatible implicit declaration of built-in function 'malloc' 也是。 【参考方案1】:

很可能是因为您不包括 malloc 的头文件,虽然编译器通常会警告您这一点,但您显式转换返回值的事实意味着您'告诉它你知道你在做什么。

这意味着编译器期望从malloc 返回一个int,然后将其转换为一个指针。如果它们的大小不同,那会让你感到悲伤。

这就是为什么您从不在 C 中强制转换 malloc 返回。它返回的 void* 将被隐式转换为正确类型的指针(除非您没有包含header 在这种情况下,它可能会警告您潜在的不安全的 int 到指针的转换)。

【讨论】:

抱歉听起来很幼稚,但我一直认为 malloc 返回一个可以转换为适当类型的 void 指针。我不是 C 程序员,因此希望了解更多细节。 @user7: 如果没有#include ,C 编译器假定 malloc 的返回值为 int。 @user7: void 指针 可以 被强制转换,但在 C 中不需要它,因为 void * 可以隐式转换为任何其他指针类型。如果适当的原型在范围内,int *p = malloc(sizeof(int)) 有效,如果不在范围内则失败(因为假设结果为 int)。使用强制转换,两者都可以编译,而后者在sizeof(int) != sizeof(void *) 时会导致错误。 @user7 但是如果你不包含stdlib.h,编译器不知道malloc,也不知道它的返回类型。所以它只是假设int 作为默认值。【参考方案2】:

转换为int* 掩盖了这样一个事实,即如果没有正确的#includemalloc 的返回类型被假定为int。 IA-64 恰好有sizeof(int) < sizeof(int*),这使得这个问题很明显。

(还要注意,由于未定义的行为,即使在sizeof(int)==sizeof(int*) 成立的平台上,它仍然可能失败,例如,如果调用约定使用不同的寄存器来返回指针而不是整数)

comp.lang.c FAQ 有一个讨论 why casting the return from malloc is never needed and potentially bad 的条目。

【讨论】:

没有正确的#include,为什么malloc的返回类型被假定为int? @WTP - 这是在 C++ 中始终使用 new 并始终使用 C 编译器而不是 C++ 编译器编译 C 的好理由。 @user7 - 这就是规则。如果未知,则任何返回类型都假定为int @vlad - 更好的办法是总是声明函数,而不是完全出于这个原因依赖隐式声明。 (而不是从malloc 投回) @user7:“我们有一个指针 p(大小为 64),它指向 32 位内存” - 错误。 malloc 分配的块的地址是根据void* 的调用约定返回的。但是调用代码认为函数返回int(因为你选择不告诉它),所以它尝试根据int的调用约定读取返回值。因此p 不一定 一定指向分配的内存。它恰好适用于 IA32,因为 intvoid* 的大小相同,并且以相同的方式返回。在 IA64 上,您会得到错误的值。【参考方案3】:

这就是为什么您在编译时永远不会没有关于缺少原型的警告。

这就是为什么你从不在 C 中强制执行 malloc 返回的原因。

C++ 兼容性需要强制转换。几乎没有理由(阅读:这里没有理由)省略它。

C++ 兼容性并非总是需要,在少数情况下根本不可能,但在大多数情况下很容易实现。

【讨论】:

为什么我会关心我的 C 代码是否与 C++“兼容”?我不在乎它是否与 perl 或 java 或 Eiffel 或 ... 兼容 如果你保证下线的人不会看你的 C 代码然后走,嘿,我会用 C++ 编译器编译它,因为那应该可以工作! 这是因为大多数 C 代码可以简单地与 C++ 兼容。

以上是关于为啥这段代码在 64 位架构上会出现段错误,但在 32 位上却能正常工作?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个基本链表可以在 MacOS 上工作,但在 Linux 上会出现段错误

为啥这段代码在 HPUX 上会崩溃?

x64:为啥这段代码给我“地址边界错误”

为啥在某些机器上堆栈溢出,但在另一台机器上出现分段错误?

汽车加油问题出现一些错误。它适用于大多数测试用例,但不适用于所有测试用例。你能告诉我为啥这段代码是错误的

为啥这段代码没有将数据插入数据库?