Home > front end >  Questions regarding va_list in C
Questions regarding va_list in C

Time:10-07

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 & instead of using it, passes it to another function that requires a va_list? (so in a pseudo code it would be like void printfRipOff(const char* format, ...) {printf(format, ...);})

CodePudding user response:

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?

Look at function vprintf( const char * format, va_list arg ); for one example of a function that takes a va_list as an input parameter.

It is basically used this way:

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

void CountingPrintf(const char* format, ...)
{
   static int counter = 1;
   va_list args;
   va_start(args, format);
   printf("Line # %d: ", counter  ); // Print a Counter up front
 
   vprintf(format, args); // Pass the "args" through to another printf function.

   va_end(args);
}

int main(void) {
    CountingPrintf("%s %s\n", "Hello", "World");
    CountingPrintf("%d   %d == %d", 2, 3, 2 3);
    
    return 0;
}

Output:

Line # 1: Hello World
Line # 2: 2   3 == 5

CodePudding user response:

The printf and scanf family of functions don't need to be explicitly passed the length of the va_list because they are able to infer the number of arguments by the format string.

For instance, in a call like:

printf("%d %c %s\n", num, c, "Hello");

printf is able to examine the first parameter, the format string, and see that it contains a %d, a %c a %s in that order, so it can assume that there are three additional arguments, the first of which is a signed int, the second is a char and the third is a char* that it may assume is null-terminated.

To write a similar function that does not need to be explicitly told how many arguments are being passed, you must find a way to subtly give it some information allowing it to infer the number and types of the arguments in va_list.

CodePudding user response:

  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.

  • Related