C中的Implict函数声明是否实际生成了对象代码?

在关于转换malloc的返回值的讨论过程中,许多人声称malloc的隐式声明会导致返回值转换为int然后重新转换回T*可能导致在以下情况下截断指针:

 sizeof(int) < sizeof(void*) 

这意味着编译器执行以下操作:

  1. 链接并调用定义malloc的正确目标代码
  2. 生成目标代码以将返回值转换为较短的int类型
  3. 生成目标代码以转换回更大的目标指针类型

有人真的能certificate这种情况发生了吗? 在64位Linux上使用一些示例代码说?

我自己做,但我无法访问64位机器。

描述发生的事情的问题在步骤2中。通过隐式声明,调用站点的代码实际上不会“转换”函数的返回值。

会发生的是,调用站点的代码通过假设它的类型为“int”来提取返回值(通常来自寄存器或堆栈外)。 对于不同的操作系统和编译器,执行此操作的过程是不同的,并且通常由ABI文档指定。

对于最常见的ABI,int和void *的返回位置和大小是相同的,所以即使它不正确,你实际上也不会有任何问题。 对于32位和64位平台上的Linux,Windows和Mac OS X 都是如此,我相信 32位平台。

在64位平台上,“long”和“void *”更常见的是相同的大小,因此如果你有一个malloc()的隐式声明,返回值将被截断。 但是,有几种流行的64位编程模型。

回到DOS开发的“旧时代”,有可能创建以“int”为16位,指针为32位(实际为24位)的模式运行的程序。 在这些情况下,使用隐式原型调用malloc()会截断返回的值。

请注意,即使在截断返回值的情况下,您仍可能没有运行时问题,具体取决于该值是否实际超出int的有效范围。


在Mac OS X上,在64位模式下,此代码:

 #include  int main (int argc, const char * argv[]) { int x = malloc(128); void *p = malloc(128); printf("Hello, World!\nsizeof(int)=%d,sizeof(void*)=%d,x=0x%xd,p=%p\n", sizeof(int), sizeof(void *), x, p); return 0; } 

打印:

你好,世界! 的sizeof(INT)= 4,的sizeof(无效*)= 8,X = 0x1001c0d,P = 0x100100240

请注意,“x”值的位数少于“p”值,而是静默地删除了值的最高32位。 两次调用malloc时的实际汇编代码如下所示:

 LM2: movl $128, %edi call _malloc movl %eax, -12(%rbp) LM3: movl $128, %edi call _malloc movq %rax, -8(%rbp) 

因此,malloc(在%rax中)返回了正确的值,但是当movl指令被移动到变量“x”时,它会截断它。

我认为2并不像你暗示的那样是“有意义的”转换。 当使用其返回类型未知的函数进行调度时,编译器必须对要“抓取”多少字节做出一些假设。 默认值是int的大小。

所以如果一个void *和一个int碰巧是相同的大小,那么好,如果不是oops!

malloc在stdlib.h文件头中声明,声明直接包含在源代码的C预处理器中,然后在后续阶段与malloc代码链接。

当你有代码时:

 #include  ... void * foo = malloc(42); 

它实际上已经过了类似的东西

 ... extern void *malloc (size_t __size) __attribute__ ((__nothrow__)) __attribute__ ((__malloc__)) ; (...lots of other declarations...) ... void * foo = malloc(42); 

如果不包含函数原型,则默认为类似

 int malloc(); ... void * foo = malloc(42); 

这意味着最终编译的代码将执行类似“使用参数42调用malloc ,将其返回值从int转换为void *并将其放入foo ”的操作。 然后这将与具有预编译的malloc目标代码的libc链接,这显然是无效的* – 返回。 因此,结果将是CPU寄存器上的一个额外的int-to-void *转换,它保存返回值。 我想在64位架构上,它可能意味着采用较低的32位并在之前放置32个零,从而清除原始指针的一部分。

通过省略malloc的声明(原型),编译器假定它返回int 。 因此调用它作为代码来生成一个返回int结果的函数。

如何完成此操作取决于您的系统,因此结果可能会传回数据寄存器,地址寄存器或堆栈。

然后编译器生成附加代码以将(假定的)返回的int值转换为指针。

显然,这不是你想要的。 你可能在大多数系统上都很幸运,其中int和指针的宽度相同,因此返回值的转换基本上什么都不做,但是你不能依赖这种行为。

总而言之, 不要声明外部函数是件坏事