Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to know last argument of va_list?

I'm reading about how to pass optional arguments to function. But I'm unable to understand those. When I see examples, they are confusing and a bit complex. So I just started with a very simple program with what I have understood up to now.

The below program just prints the variables.

void print(int x, ...)
{
        va_list ap;
        int i = 4;     // I know I'm passing only 4 opt variables. 
        int num;

        va_start(ap, x);
        while(i--) {         // How to know how many variables came in real time?
                num = va_arg(ap, int);
                printf("%d\n", num);
        }
        va_end(ap);
        return;
}

int main()
{
        print(1,2,3,4,5);
        return 0;

}

I don't know above program is right or not. But it's working. When I change the i value to 5 printing garbage. How to know how many arguments I got (like argc in main)?

like image 942
gangadhars Avatar asked May 14 '14 06:05

gangadhars


People also ask

What is Va ARG?

The va_arg() macros are used to pass a variable number of arguments to a function. First, you must have a call to va_start() passing a valid va_list and the name of the last argument variable before the ellipsis ("...").

Where is Va_arg defined?

Defined in header <stdarg.h> T va_arg( va_list ap, T ); The va_arg macro expands to an expression of type T that corresponds to the next parameter from the va_list ap .

What is a VA list?

va_list is a complete object type suitable for holding the information needed by the macros va_start, va_copy, va_arg, and va_end. If a va_list instance is created, passed to another function, and used via va_arg in that function, then any subsequent use in the calling function should be preceded by a call to va_end.

Can va_list be null?

Since va_list is a thingy, you can't assign a NULL. Basically va_list is a C++ class that has no constructors, no operator==, no operator void*, etc.


2 Answers

There is no way of knowing how many arguments are passed from inside a variable-argument function, that's why functions such as printf are using special format strings that tells the function how many arguments to expect.

Another way is of course to pass the number of "extra" arguments as the first argument, like

print(4, 1, 2, 3, 4);

Or to have a special value that can't be in the list as last argument, like

print(1, 2, 3, 4, -1);

You also have to take note that the last non-va argument you pass to the function (the argument named num in your case) is not included in the va_list, so in your case with the shown code with 4 as hardcoded number of arguments you will still print garbage, as you pass 1 for the num argument and then three va_list arguments.

Also take care because you use num as both argument and a local variable name.

like image 161
Some programmer dude Avatar answered Sep 23 '22 14:09

Some programmer dude


You can take a look to NARGS macro

Adapted to your code:

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

#define NARGS_SEQ(_1,_2,_3,_4,_5,_6,_7,_8,_9,N,...) N
#define NARGS(...) NARGS_SEQ(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1)

#define print(...) fnprint(NARGS(__VA_ARGS__), __VA_ARGS__)

void fnprint(int n, ...)
{
        va_list ap;
        int num;

        va_start(ap, n);
        while (n--) {
                num = va_arg(ap, int);
                printf("%d\n", num);
        }
        va_end(ap);
        return;
}

int main(void)
{
        print(1, 2, 3, 4, 5);
        return 0;
}

EDIT: If you want to use the same name for macro and function, use () to stop the preprocessor from expanding the function definition:

#define print(...) print(NARGS(__VA_ARGS__), __VA_ARGS__)

void (print)(int n, ...) /* Note () around the function name */
{
  ...

EDIT 2: Another (ugly) method (but without restriction in the number of args) using compound literals (std 99) and sizeof:

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

#define print(...) print(sizeof((int []) {__VA_ARGS__}) / sizeof(int), __VA_ARGS__)

void (print)(int n, ...)
{
    va_list ap;
    int num;

    va_start(ap, n);
    while (n--) {
        num = va_arg(ap, int);
        printf("%d\n", num);
    }
    va_end(ap);
    return;
}

int main(void)
{
    print(1, 2, 3, 4, 5);
    return 0;
}

print is expanded to:

print(sizeof((int []) {1, 2, 3, 4, 5}) / sizeof(int), 1, 2, 3, 4, 5);

But constructions like print(1, 2, 3, a_var++, 4, 5); or print(1, 2, some_func_returning_int(), 4, 5); evaluates a_var++ and some_func_returning_int() two times, thats a big problem.

like image 36
David Ranieri Avatar answered Sep 20 '22 14:09

David Ranieri