va_arg 64bit issue

放肆的年华 提交于 2019-12-11 08:09:33

问题


I've such C code. On 64-bit linux system the result is: 4294967264 instead of -32. Both clang and gcc produce binary with same incorrect results. The problem in the line:

*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;
}

回答1:


You actually need to pass a long to your function. You're passing an int.

setter(&v, -32L);



回答2:


On a x86_64 architecture, the size of long is 64 bit. When you are passing -32 to setter(), its type is int and is only 32-bit. If you want long to be passed, cast it explicitly. For example:

setter((long *) &v, (long)-32);



回答3:


A little clarification:
As said, on 64-bit architectures, a long is 64-bit. That is not the whole story, however, since C/C++ does some automatic conversion. Here, the setter() function accepts one specified argument and zero or more unspecified arguments. The -32 argument is one of those unspecified arguments, therefore the compiler does not know that in fact a long is intended and retains an int (32-bit). Also, an extra zero is pushed onto the stack to maintain 64-bit alignment. This will produce the printed result as stated above. Therefore, you must explicitly specify that you want a long here (either -32L or (long) -32). But if the function actually had been declared void setter (long *v, long arg), then the compiler would have known that the second argument is a long, and automatically converted the int argument.




回答4:


Even more strange, but still not a real compiler bug: If you pass the -32 value as 7th or later variadic argument, it may be expanded to 64bit:

#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;
}

And the output on x64 Linux is:

0xffffffffffffffe0  -32
0x00000000ffffffe0  4294967264
0x00000000ffffffe0  4294967264
0x00000000ffffffe0  4294967264
0x00000000ffffffe0  4294967264
0x00000000ffffffe0  4294967264
0xffffffffffffffe0  -32
0xffffffffffffffe0  -32

However, the output on x64 Windows is something like:

0xffffffffffffffe0  -32
0x00000000ffffffe0  4294967264
0x00000000ffffffe0  4294967264
0x00000000ffffffe0  4294967264
0x00040800ffffffe0  1134700294832096
0x178bfbffffffffe0  1696726761565323232
0x00007ff6ffffffe0  140698833649632
0x00007ff6ffffffe0  140698833649632

The reason here is that in the 64bit x86 calling conventions, a number of leading arguments (6 in System V AMD64 ABI, 4 in Microsoft x64) is passed via CPU registers, and only subsequent arguments are passed via the stack.

As the 64bit registers provide separate access to their lower 32bit, the compiler does set their lower 32bits only, so there is no value expansion to 64bit.

For the subsequent arguments, it depends on the compiler:

  • gcc does push expanded 64bit values onto the stack
  • MSVC cl.exe does write each lower 32bits onto the stack, leaving each upper 32bits uninitialized there
  • not sure about other compilers

In any case, there are 64bits reserved for each argument on the stack.



来源:https://stackoverflow.com/questions/15480927/va-arg-64bit-issue

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!