2

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?

1
  • 2
    The GCC 4.7 compiler on Linux/Debian/x86-64 when invoked with gcc -Wall -g fti.c -o fti rightly warns: fti.c:7:5: warning: second parameter of 'va_start' not last named argument [enabled by default] Commented Feb 3, 2013 at 15:29

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Actually, the address of the last argument is utterly useless in a modern implementation where arguments might not be on the stack, or where even if they are, the compiler might choose not to use the original "stack slot" as the local version of the argument. The convention of needing to pass an address to va_start came from an ancient time when C was pretty much "high level assembler". On modern implementations, a special compiler builtin is needed in order to provide the va_start macro.
To clarify, however, nothing is wrong with this answer. Formally, the address is needed to satisfy the interface requirements of the va_start macro, even though it should not need to use this address for anything internally. Passing the wrong address results in UB.
1

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.

1 Comment

One of the things that made C unique was that many older implementations documented how they would process various constructs in sufficient detail that the language could support things like variadic functions without compilers having to know or care about whether functions were variadic, or whether code was using pointer manipulations to access the variadic part of the address list.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.