Search code examples
cstandard-library

va_list in C: Creating a function that doesn't need a argument count like 'printf'


Using the <stdarg.h> header, one can make a function that has a variable number of arguments, but:

  1. To start using a va_list, you need to use a va_start macro that needs to know how many arguments there, but the printf & ... that are using va_list don't need the argument count. How can I create a function that doesn't need the argument count like printf?

  2. Let's say I want to create a function that takes a va_list and instead of using it, passes it to another function that requires a va_list? (so in pseudocode, it would be like void printfRipOff(const char* format, ...) {printf(format, ...);})


Solution

    1. Functions like printf() and scanf() have the format string argument that tells them the number and types of the arguments that must have been provided by the function call.

      If you're writing your own function, you must have some way of knowing how many arguments were provided and their types. It may be that they are all the same type. It may be that they're all pointers and you can use a null pointer to indicate the end of the arguments. Otherwise, you probably have to include a count.

    2. You can do that as long as provided the called function expects a va_list. There are caveats (see C11 §7.16 Variable arguments) but they're manageable with a little effort.

    A very common idiom is that you have a function int sometask(const char *fmt, ...) and a second function int vsometask(const char *fmt, va_list args), and you implement the first as a simple call to the second:

    int sometask(const char *fmt, ...)
    {
        va_list args;
        va_start(fmt, args);
        int rc = vsometask(fmt, args);
        va_end(args);
        return rc;
    }
    

    The second function does the real work, of course, or calls on other functions to do the real work.


    In the question, you say:

    va_start macro that needs to know how many arguments …

    No; the va_start macro only needs to know which argument is the one before the ellipsis — the argument name before the , ... in the function definition.


    In a comment, you say:

    This is what I'm trying to write, but it didn't work.

    string format(const char* text, ...)
    {
        // temporary string used for formatting
        string formattedString;
        initializeString(&formattedString, text);
        // start the va_list
        va_list args;
        va_start(text, args);
        // format
        sprintf(formattedString.array, text, args);
        // end the va_list
        va_end(args);
        return formattedString;
    }
    

    As noted by abelenky in a comment, you need to use vsprintf():

    string format(const char* text, ...)
    {
        string formattedString;
        initializeString(&formattedString, text);
        va_list args;
        va_start(text, args);
        vsprintf(formattedString.array, text, args);
        va_end(args);
        return formattedString;
    }
    

    That assumes that the formattedString has enough space in the array for the formatted result. It isn't immediately obvious how that's organized, but presumably you know how it works and it works safely. Consider using vsnprintf() if you can determine the space available in the formattedString.