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

时间:2009-10-14 13:05:28

标签: c compiler-construction

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

sizeof(int) < sizeof(void*)

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

  1. 链接并调用定义malloc
  2. 的正确对象代码
  3. 生成目标代码以将返回值转换为较短的int类型
  4. 生成目标代码以转换回更大的目标指针类型
  5. 有人真的可以证明这种情况发生了吗?说一下64位Linux上的一些示例代码?

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

4 个答案:

答案 0 :(得分:6)

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

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

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

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

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

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


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

#include <stdio.h>

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”时,它会截断它。

答案 1 :(得分:1)

Malloc在stdlib.h文件头中声明,声明直接包含在您的源代码的C preprocessor中,然后在稍后阶段与malloc代码链接。

当你有代码时:

#include <stdlib.h>
...
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个零,从而清除原始指针的一部分。

答案 2 :(得分:1)

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

所以如果一个void *和一个int恰好相同的大小,那就好了,如果不是oops!

答案 3 :(得分:1)

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

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

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

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

总而言之,不宣布外部函数是件坏事。