Why does my variadic function work with int and long long?
As per this answer, numeric constants passed to variational functions are always treated as int
if they fit into one. This makes me wonder why the following code works with both, int
and long long
. Consider the following function call:
testfunc(4, 1000, 1001, 1002, 1003);
testfunc
as follows:
void testfunc(int n, ...)
{
int k;
va_list marker;
va_start(marker, n);
for(k = 0; k < n; k++) {
int x = va_arg(marker, int);
printf("%d\n", x);
}
va_end(marker);
}
This works great. It prints 1000, 1001, 1002, 1003. But to my surprise, the following code works:
void testfunc(int n, ...)
{
int k;
va_list marker;
va_start(marker, n);
for(k = 0; k < n; k++) {
long long x = va_arg(marker, long long);
printf("%lld\n", x);
}
va_end(marker);
}
Why? Why does he work with long long
too? I thought that numeric integer constants are passed as int
if they fit into one? (see link above). How can it be that he works with long long
too?
Hell, it works even when alternating between int
and long long
. This is confusing me:
void testfunc(int n, ...)
{
int k;
va_list marker;
va_start(marker, n);
for(k = 0; k < n; k++) {
if(k & 1) {
long long x = va_arg(marker, long long);
printf("B: %lld\n", x);
} else {
int x = va_arg(marker, int);
printf("A: %d\n", x);
}
}
va_end(marker);
}
How can it be? I thought all my parameters were passed as int
... why can I randomly switch between int
and long long
without any problem? I'm really confused now ...
Thanks for any light on this!
It has nothing to do with C. It's just that the system you are using (x86-64) passes the first few arguments in 64-bit registers, even for variadic arguments.
Essentially, in the architecture being used, the compiler generates code that uses the full 64-bit case for every argument, including variable arguments. This ABI agrees with the architecture and has nothing to do with C per se; all programs, no matter how they are built, must follow the ABI in the architecture that it must execute.
If you are using Windows, x86-64 uses rcx
, rdx
, r8
and r9
for the first four (or integer index) arguments in this manner and for the rest of the stack. In Linux, BSD, Mac OS X and Solaris, x86-64 uses rdi
, rsi
, rdx
, rcx
, r8
and r9
for the first six (or integer index) argument, in that order, and for the rest of the stack.
You can check this with a trivial example program:
extern void func(int n, ...);
void test_int(void)
{
func(0, 1, 2);
}
void test_long_long(void)
{
func(0, 1LL, 2LL);
}
If you compile the above x86-64 assembly (for example gcc -Wall -O2 -march=x86-64 -mtune=generic -S
) on Linux, BSD, Solaris, or Mac OS (X or newer), you get approximately (AT&T syntax, source, target operand)
test_int: movl $2, %edx movl $1, %esi xorl %edi, %edi xorl %eax, %eax jmp func test_long_long: movl $2, %edx movl $1, %esi xorl %edi, %edi xorl %eax, %eax jmp func
i.e. functions are identical and do not drag arguments onto the stack. Note that the jmp func
equivalent call func; ret
is simpler.
However, if you compile for x86 ( -m32 -march=i686 -mtune=generic
), you get something like
test_int: subl $16, %esp pushl $2 pushl $1 pushl $0 call func addl $28, %esp ret test_long_long: subl $24, %esp pushl $0 pushl $2 pushl $0 pushl $1 pushl $0 call func addl $44, %esp ret
which shows that the x86 calling conventions in Linux / BSD / etc. They include transmitting variational argument on the stack, and that embodiment int
pushes a 32-bit constants to the stack ( pushl $x
pushing a 32-bit constant x
in the stack), and a variant long long
pushes the stack 64 bits.
Therefore, due to the underlying ABI of the operating system and architecture you are using, your variational function shows the "anomaly" that you observed. To see the behavior you'd expect from the C standard alone, you need to work around the underlying quirk ABI - for example, run your variadic functions with at least six arguments to occupy registers on x86-64 architectures so that the rest are truly variable arguments are passed onto the stack.