Search code examples
cvariadic-functionssignedunsigned-integerinteger-promotion

Promotion of integer in variadic functions


Why does the first f() call in the code below end up printing the -2 that I have passed to it as 4294967294 instead?

#include <stdio.h>
#include <stdint.h>
#include <stdarg.h>
#include <stdlib.h>

void f(int64_t i, ...)
{
    va_list ap;
    int64_t j;

    va_start(ap, i);
    j = va_arg(ap, int64_t);
    printf("%jd %jd\n", (intmax_t) i, (intmax_t) j);
    va_end(ap);
}

int main()
{
    f(-1, -2);           // Prints -1 4294967294 (bug!)
    f(-1, (int64_t) -2); // Prints -1 -2 (fix!)
    return 0;
}

I can understand why the second f() call with the fix works. But I can't understand why the first f() call causes this issue. Can you explain this behavior?


Solution

  • In the first call, the argument is passed as an int. Unless int is represented by using 64 bits, you invoke undefined behavior by trying to extract a int64_t from it. Judging by the output from your program int is not presented using 64 bits on your platform.

    From the Section 7.16.1.1/2 C11 Standard (emphasis mine):

    The va_arg macro expands to an expression that has the specified type and the value of the next argument in the call. The parameter ap shall have been initialized by the va_start or va_copy macro (without an intervening invocation of the va_end). Each invocation of the va_arg macro modifies ap so that the values of successive arguments are returned in turn. The parameter type shall be a type name specified such that the type of a pointer to an object that has the specified type can be obtained simply by postfixing a * to type. If there is no actual next argument, or if type is not compatible with the type of the actual next argument (as promoted according to the default argument promotions), the behavior is undefined, except for the following cases:

    — one type is a signed integer type, the other type is the corresponding unsigned integer type, and the value is representable in both types;

    — one type is pointer to void and the other is a pointer to a character type.