1

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;
}
m7913d
  • 10,244
  • 7
  • 28
  • 56
brovko
  • 73
  • 1
  • 7

4 Answers4

4

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

setter(&v, -32L);
Mat
  • 202,337
  • 40
  • 393
  • 406
3

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);
  • -32L is the same and without cast. – brovko Mar 18 '13 at 16:09
  • Note that `long` is not guaranteed (by the C++ standard) to be 64bit on all x86_64 bit platforms. F.ex. on Windows `long` is 32bit (https://stackoverflow.com/questions/384502/what-is-the-bit-size-of-long-on-64-bit-windows). – m7913d Jun 07 '21 at 14:09
1

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.

  • Note that `long` is not guaranteed (by the C++ standard) to be 64bit on all x86_64 bit platforms. F.ex. on Windows `long` is 32bit (https://stackoverflow.com/questions/384502/what-is-the-bit-size-of-long-on-64-bit-windows). – m7913d Jun 07 '21 at 14:10
0

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.

haubi
  • 1
  • 1