KianFakheriAghdam
KianFakheriAghdam

Reputation: 166

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, ...);})

Upvotes: 4

Views: 831

Answers (3)

Willis Hershey
Willis Hershey

Reputation: 1574

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, and 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 a pointer to a null-terminated string.

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.

Upvotes: 2

abelenky
abelenky

Reputation: 64710

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;
   printf("Line # %d: ", counter++); // Print a Counter up front

   va_list args;
   va_start(args, format); // Get the args
   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\n", 2, 3, 2+3);
    
    return 0;
}

Output:

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

Upvotes: 3

Jonathan Leffler
Jonathan Leffler

Reputation: 754570

  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.

Upvotes: 2

Related Questions