va_arg 64位问题

时间:2013-03-18 15:43:59

标签: c 64-bit variadic-functions

我有这样的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;
}

4 个答案:

答案 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位。

对于后续参数,取决于编译器:

  • gcc确实将扩展的64位值压入堆栈
  • MSVC cl.exe确实将每个低32位写入堆栈,而每个高32位都未初始化在那里
  • 不确定其他编译器

无论如何,堆栈上的每个参数都保留64位。