10

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)?

gangadhars
  • 2,584
  • 7
  • 41
  • 68
  • 1
    Same way you know how many items are in an array you passed to a function; you keep track of it yourself, because C isn't going to do it for you. – user2357112 May 14 '14 at 06:28
  • `How to know how many arguments I got (like argc in main)?` - **you don't.** You pass it along with the arguments. Maybe as the first argument. And please **do not** try to follow the "awesome" ideas you've got from the answer below. They're truly, truly horrible. – The Paramagnetic Croissant May 14 '14 at 10:57

3 Answers3

10

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.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
8

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.

Community
  • 1
  • 1
David Ranieri
  • 39,972
  • 7
  • 52
  • 94
  • rename `fnprint` to `print` – gangadhars May 14 '14 at 08:26
  • 1
    sorry I didn't see line `#define print(...) fnprint(NARGS..` line. Thats way I said. Now It's OK. And worked. But I didn't get what NARGS does exactly. – gangadhars May 14 '14 at 08:59
  • @SGG, thanks for check this answer, but note that `print` is restricted to `N` args, Joachim's answer (`pass the number of "extra" arguments as the first argument`) doesn't have restrictions – David Ranieri May 14 '14 at 09:06
  • your code only supports only 9 numbers. If it's more than 9 numbers, segmentation fault will come. right? – gangadhars May 14 '14 at 09:11
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/52655/discussion-between-sgg-and-alter-mann) – gangadhars May 14 '14 at 09:29
  • But of course you can increase the number of args, `__VA_ARGS__ ,64, 63,62` – David Ranieri May 14 '14 at 09:30
  • In the chat the conclusion is: `print(0, 1, 2, 0, 3, 4, 5, 10, 324, 5325, 3525, 532, 12, 313, 321);` is expanded to: `print(5325, 0, 1, 2, 0, 3, 4, 5, 10, 324, 5325, 3525, 532, 12, 313, 321);` giving a segmentation fault – David Ranieri May 14 '14 at 09:43
  • Evaluates params two times, a problem when you use functions instead of constant numbers, see last edit – David Ranieri May 14 '14 at 10:14
  • 2
    @SGG The first method has a limited number of parameters and the seconds only takes a specific type. Here is a solution to both problems: http://stackoverflow.com/a/20993120/2327831 ( shameless self promotion ) – this May 14 '14 at 10:18
  • The example only takes void pointers but you can pass anything. – this May 14 '14 at 10:24
0

Another ugly way for both int and strings:

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

#define print(...) fnprint("{" # __VA_ARGS__ )

fnprint(char *b) {
    int count = 0, i;
    if (b[1] != '\0') {
        for (i =2; b[i]; i++) {
            if (b[i] == ',')
               count++;
        }
        count++;
    }
    printf("\ncount is %i\n", count);
}

int main(void)
{
        print();
        print("1", "2");
        print(1, 2, 3, 4, 5);
        return 0;
}
Pointer
  • 627
  • 2
  • 8
  • 19
  • your receiving them as `char`. If I want to manipulate received variables, I have to convert them to particular type. My code will be huge – gangadhars May 14 '14 at 11:55
  • 1
    this function would be just utility to count then pass this val and VA_ARGS separately to other function: #define print(...) PRINT( COUNTINGFUNCTION("{" # __VA_ARGS__ ), __VA_ARGS__ ) – Pointer May 14 '14 at 13:17