在此讨论过程中about casting the return value of malloc
许多人声称malloc
的隐式声明会导致返回值转换为int
,然后重新转换回T*
可能导致在以下情况下截断指针:
sizeof(int) < sizeof(void*)
这意味着编译器会执行以下操作:
malloc
有人真的可以证明这种情况发生了吗?说一下64位Linux上的一些示例代码?
我自己做,但我无法访问64位机器。
答案 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
值转换为指针。
显然,这不是你想要的。你可能在大多数系统上都很幸运,其中整数和指针的宽度相同,因此返回值的转换基本上什么都不做,但你不能依赖这种行为。
总而言之,不宣布外部函数是件坏事。