Reputation: 47911
I'm using Visual Studio 2012 to compile this sample code:
#include <stdarg.h>
#include <stdio.h>
const char * __cdecl foo(const char * format, const char * requiredArgument, ...)
{
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
return requiredArgument;
}
int main(int, char **)
{
foo("The %s is %d pixels wide and %d pixels high.", "box", 300, 200);
return 0;
}
The debug build of the program terminates normally after printing the message "The box is 300 pixels wide and 200 pixels high.".
The release build crashes with a segmentation fault.
My interpretation for this behavior - but I may be wrong about that, please correct me if so - is that I'm incorrectly specifying a function parameter other than the last non-variadic one in va_start
, the only admissible form being here va_start(args, requiredArgument)
rather than va_start(args, format)
as I would like to have.
In other words, I'm misusing va_start
in a way that makes the whole program flow unpredictable, and so the segmentation fault is nothing but fine here.
If my assumptions are right, I have two questions now:
Why is it even required to specify the last formally declared function parameter in va_start
, if choosing anything else is apparently illegal?
Why does the picky VC++ compiler not raise a warning for such an easy to detect and potentially critical pitfall?
Upvotes: 2
Views: 1248
Reputation: 60145
This is explained in the C89 rationale.
The parmN argument to va start is an aid to writing conforming ANSI C code for existing C implementations. Many implementations can use the second parameter within the structure of existing C language constructs to derive the address of the first variable argument. (Declaring parmN to be of storage class register wouldinterfere with use of these constructs; hence the effect of such a declaration is un-defined behavior. Other restrictions on the type of parmN are imposed for the same reason.) New implementations may choose to use hidden machinery that ignores the second argument to va_start ...
Older machines used reverse-order stack passing, so if you knew the address of the argument before ...
, the ...
could be addressed via pointer arithmetic using something like (char*)&lastArgBeforeTheEllipsis + sizeof(char*)
and it was sure to be there in stack memory, having been placed there by the function's caller. On newer ABIs such as x86-64 SysV, you get mixed register/stack passing, so va_start
can't be expressed in terms of lastArgBeforeEllipsis
and common C constructs but rather the compiler itself needs to know which registers to dump given the calling function's signature. There you get va_start
defined to some __buitin_va_start
and the second argument remains just for the lols and standard conformance.
Upvotes: 1
Reputation:
Why is it even required to specify the last formally declared function parameter in va_start, if choosing anything else is apparently illegal?
Because that macro needs to know the address of the last argument.
Why does the picky VC++ compiler not raise a warning for such an easy to detect and potentially critical pitfall?
Because it's just not "intelligent" enough. Or its creators decided not to include this warning. Or maybe it could, but by default it's turned off and you can turn it on using some compiler flag.
Upvotes: 2