我有这样的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;
}
答案 0 :(得分:4)
您实际上需要将long
传递给您的函数。您正在传递int
。
setter(&v, -32L);
答案 1 :(得分:3)
在x86_64架构上,long
的大小为64位。当您将-32
传递给setter()
时,其类型为int
且仅为32位。如果您希望传递long
,请明确地投射它。例如:
setter((long *) &v, (long)-32);
答案 2 :(得分:1)
稍作澄清:
如上所述,在64位体系结构中,long
是64位。然而,这不是全部,因为C / C ++会进行一些自动转换。这里,setter()
函数接受一个指定的参数和零个或多个未指定的参数。 -32参数是那些未指定的参数之一,因此编译器不知道实际上long
是预期的并且保留int
(32位)。此外,将额外的零推入堆栈以保持64位对齐。这将产生如上所述的打印结果。因此,您必须在此处明确指定您需要long
(-32L
或(long) -32
)。但是如果函数实际上已经声明为void setter (long *v, long arg)
,那么编译器就会知道第二个参数是long
,并自动转换int
参数。
答案 3 :(得分:0)
更奇怪,但仍然不是真正的编译器错误:
如果您将-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位。