我有这样的C代码。在64位Linux系统上,结果是:4294967264而不是-32。 clang和gcc都生成具有相同错误结果的二进制文件。行中的问题:
*v = va_arg(args, long);
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
void setter(long *v, ...)
{
va_list args;
va_start(args, v);
*v = va_arg(args, long);
va_end(args);
}
int main()
{
long v = 0;
setter((long *) &v, -32);
printf("%ld\n", v);
return 0;
}
你实际上需要将long
传递给你的函数。你正在通过int
。
setter(&v, -32L);
在x86_64架构上,long
的大小为64位。当你将-32
传递给setter()
时,它的类型是int
,只有32位。如果你想要传递long
,请明确地投射它。例如:
setter((long *) &v, (long)-32);
一点澄清:
如上所述,在64位架构中,long
是64位。然而,这不是全部,因为C / C ++会进行一些自动转换。这里,setter()
函数接受一个指定的参数和零个或多个未指定的参数。 -32参数是那些未指定的参数之一,因此编译器不知道实际上是long
并且保留int
(32位)。此外,将额外的零推入堆栈以保持64位对齐。这将产生如上所述的打印结果。因此,您必须在此处明确指定您需要long
(-32L
或(long) -32
)。但是如果函数实际上已经被声明为void setter (long *v, long arg)
,那么编译器就会知道第二个参数是long
,并自动转换了int
参数。
更奇怪的是,但仍然不是真正的编译器错误:如果你将-32
值作为第7个或更后的可变参数传递,它可能会扩展到64位:
#include <stdio.h>
#include <stdarg.h>
/* long long int is a 64bit datatype on both Unix and Windows */
void setter(long long int arg, ...)
{
va_list args;
va_start(args, arg);
while(arg != 0) {
printf("0x%016llx %lld\n", arg, arg);
arg = va_arg(args, long long int);
}
va_end(args);
}
int main()
{
setter(-32, -32, -32, -32, -32, -32, -32, -32, 0);
return 0;
}
x64 Linux上的输出是:
0xffffffffffffffe0 -32
0x00000000ffffffe0 4294967264
0x00000000ffffffe0 4294967264
0x00000000ffffffe0 4294967264
0x00000000ffffffe0 4294967264
0x00000000ffffffe0 4294967264
0xffffffffffffffe0 -32
0xffffffffffffffe0 -32
但是,x64 Windows上的输出类似于:
0xffffffffffffffe0 -32
0x00000000ffffffe0 4294967264
0x00000000ffffffe0 4294967264
0x00000000ffffffe0 4294967264
0x00040800ffffffe0 1134700294832096
0x178bfbffffffffe0 1696726761565323232
0x00007ff6ffffffe0 140698833649632
0x00007ff6ffffffe0 140698833649632
这里的原因是在64位x86 calling conventions中,许多主要参数(System V AMD64 ABI中的6个,Microsoft x64中的4个)通过CPU寄存器传递,并且只有后续参数通过堆栈传递。
由于64位寄存器提供对低32位的单独访问,因此编译器仅设置其低32位,因此没有值扩展到64位。
对于后续参数,它取决于编译器:
在任何情况下,堆栈上的每个参数都有64位保留。