简体   繁体   中英

Variadic function passing long but reading as va_arg(argList, int)

I'm in the process of converting a 32 bit application to 64 bit, one of the pain points I'm running to is variadic functions that expect a long but are potentially passed an integer eg argument is hardcoded as -1 instead of -1L stemming from the 64 bit change of long size to 64 bits. Take this example code:

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

long varargsExample(int input, ...);

int main(int argc, char **argv)
{
    varargsExample(5,
    "TestInt", 0,
    /* This will fail if read as a long */
    "TestIntNegative", -1,
    "TestLong", 0L,
    "TestLongNegative", -1L,
    NULL); 
}

long varargsExample(int firstArg, ...)
{
    va_list args;
    char * name;
    long nextValue;

    va_start(args, firstArg);
    while ((name = va_arg(args, char *)) != 0)
    {
        /* If the type is changed to read in an int instead of long this works */
        nextValue = va_arg(args, long);

        printf("Got [%s] with value [%ld]\n", name, nextValue);

    }
    va_end(args);
    return 0;

}

Running this when compiled with GCC 64 bit results in:

Got [TestInt] with value [0]
Got [TestIntNegative] with value [4294967295]
Got [TestLong] with value [0]
Got [TestLongNegative] with value [-1]

Which makes sense because I'm guessing this is getting interpreted as:

0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 1111

So padding an additional 32 bits to represent the long and we get 2^32 - 1 instead of a negative number. What I'm wondering however is if I change the va_arg read to read the value as an int this appears to work regardless of whether an int or long is passed eg:

nextValue = va_arg(args, int);

Is this a hack that just happens to work or is there something in the C specification that makes it so this will work consistently? Note that this application runs on both Unix/Linux and Windows there on Windows long is 32 bits so I'm not worried about the function being passed a value that can't be represented by a 32 bit integer. I created a basic unit test that goes through INT_MIN --> INT_MAX passing to a variadic function a mix of integers/longs and reading as va_arg(args, int) and it appears to work (tested on AIX, Solaris, and RHEL), but I'm not sure if this is just undefined behavior that happens to work on these platforms.

The correct fix here is to identify all the callers of this function and make sure they're passing a long in all cases but the use of these functions is fairly widespread/hard to identify without compiler support for identifying this. I'm trying to see as an alternative if there's a GCC extension I can take advantage of to specify custom variadic type checking similar to what is done for format argument checking (sprintf, printf etc.).

The compiler does not know which types the variadic function fetches from the list, so it as to rely on the types of arguments given. It performs the default argument promotions on the arguments.

For integer types, thes basically promote "smaller" types to int or unsigned , and pass int / unsigned and "larger" types unchanged.

When fetching argument, it is your responsibility to fetch the correct type from the variadic arguments. Anything else invokes undefined behaviour .

So, as you do not pass a long , but an int you have to fetch an int . The fault will likely get unnoticed (as you suspected) if both types have the same representation.

However, the other way 'round should also not work: taking a smaller int if a larger long has been pushed. However, for typical implementations, this will only be noticed when fetching the next argument. Either way, as this is all undefined behaviour , it is vital to avoid.

gcc has some support using function __attribute__ s for printf / scanf -like format strings, but as the caller of your functions give no hint to the caller about the types, you are lost with respect to compiler-support (how should it know?).

Functions like the one you present are a common source of rioting programs and should best be avoided, because they are prone to typographic errors exactly like to one you have noticed now. It would be better to pass an array of struct to a proper function or call a fixed-argument function. They are often a radioactive legacy from times programmer fought for every line of code, be it run-time or size.

One alternative might be C11 using a macro with _Generic calling fixed-size functions for the various argument types.

If your compiler supports C99, you can change the variadic function to a function which accepts a single argument supplied as a compound literal . Such literals can be arrays of unspecified length, so you could do the following:

#include <stdio.h>

typedef struct NameAndLong {
    const char* name;
    long value;
} NameAndLong;

long varargsExample(NameAndLong things[]);

int main(int argc, char **argv)
{
    varargsExample((NameAndLong[]){
      {"TestInt", 0},
      {"TestIntNegative", -1},
      {"TestLong", 0},
      {"TestSomethingBig",1L<<62},
      {"TestLongNegative", -1},
      {NULL}});
    return 0;
}

long varargsExample(NameAndLong things[])
{
    const char * name;
    long nextValue;
    while ((name = things->name) != 0)
    {
        nextValue = things++->value;
        printf("Got [%s] with value [%ld]\n", name, nextValue);
    }
    return 0;
}

Of course, you'll have to replace all the function calls, but if you don't replace one, the compiler will tell you because there will be a clear prototype mismatch. And then you won't have to worry about people adding new calls and forgetting to add the L .

Personally, I find the extra pairing braces help with legibility, but the cast is a bit bulky. You might want to use a macro, but beware of the fact that macro arguments are separated by any comma not enclosed in parentheses . Braces don't count. You can get around that issue by using a variadic macro :

#define VA_EXAMPLE(...) (varargsExample((NameAndLong[]){__VA_ARGS__}))
// ...
VA_EXAMPLE({"TestInt", 0},
           {"TestIntNegative", -1},
           {"TestLong", 0},
           {NONE});

Here it is live on ideone (with long long instead of long , to match the compilation environment.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM