Below is code which includes a variadic function and calls to the variadic function. I would expect that it would output each sequence of numbers appropriately. It does when compiled as a 32-bit executable, but not when compiled as a 64-bit executable.
#include <stdarg.h> #include <stdio.h> #ifdef _WIN32 #define SIZE_T_FMT '%Iu' #else #define SIZE_T_FMT '%zu' #endif static void dumpargs(size_t count, ...) { size_t i; va_list args; printf('dumpargs: argument count: ' SIZE_T_FMT '\n', count); va_start(args, count); for (i = 0; i < count; i++) { size_t val = va_arg(args, size_t); printf('Value=' SIZE_T_FMT '\n', val); } va_end(args); } int main(int argc, char** argv) { (void)argc; (void)argv; dumpargs(1, 10); dumpargs(2, 10, 20); dumpargs(3, 10, 20, 30); dumpargs(4, 10, 20, 30, 40); dumpargs(5, 10, 20, 30, 40, 50); return 0; }
Here is the output when compiled for 64-bit:
dumpargs: argument count: 1 Value=10 dumpargs: argument count: 2 Value=10 Value=20 dumpargs: argument count: 3 Value=10 Value=20 Value=30 dumpargs: argument count: 4 Value=10 Value=20 Value=30 Value=14757395255531667496 dumpargs: argument count: 5 Value=10 Value=20 Value=30 Value=14757395255531667496 Value=14757395255531667506
Edit:
Please note that the reason the variadic function pulls size_t out is because the real-world use of this is for a variadic function that accepts a list of pointers and lengths. Naturally the length argument should be a size_t. And in some cases a caller might pass in a well-known length for something:
void myfunc(size_t pairs, ...) { va_list args; va_start(args, count); for (i = 0; i < pairs; i++) { const void* ptr = va_arg(args, const void*); size_t len = va_arg(args, size_t); process(ptr, len); } va_end(args); } void user(void) { myfunc(2, ptr1, ptr1_len, ptr2, 4); }
Note that the 4 passed into myfunc might encounter the problem described above. And yes, really the caller should be using sizeof or the result of strlen or just plain put the number 4 into a size_t somewhere. But the point is that the compiler is not catching this (a common danger with variadic functions).
The right thing to do here is to eliminate the variadic function and replace it with a better mechanism that provides type safety. However, I would like to document this problem, and collect more detailed information as to exactly why this problem exists on this platform and manifests as it does.
So basically, if a function is variadic, it must conform to a certain calling convention (most importantly, the caller must clean up args, not the callie, since the callie has no idea how many args there will be).
The reason why it starts happening on the 4th is because of the calling convention used on x86-64. To my knowledge, both visual c++ and gcc use registers for the first few parameters, and then after that use the stack.
I am guessing that this is the case even for variadic functions (which does strike me as odd since it would make the va_* macros more complicated).
On x86, the standard C calling convention is the use the stack always.