views:

170

answers:

3

Let's say I have a variadic function foo(int tmp, ...), when calling foo function I need to know how many arguments there are. I'm aware of two ways of finding out how many arguments there are:

  1. Use a final argument when calling foo, like -1, so your function call will be like this: foo(tmp, 1, 2, 9, -1) and when you are inside foo and a va_arg call returns -1 you know you have read all the function arguments

  2. Add one more argument in foo where the programmer will have the total number of arguments, so you will have calls to foo like this: foo(tmp, 5, 1, 2, 3, 4, 5) or foo(tmp, 2, 7, 8)

I used to follow the first way and once had the following bug. With the code:

expr_of_type(expr, boolexpr_e, newtable_e, nil_e, -1)

where expr_of_type was a variadic function and was checking if expr(the first argument) was one of the following types (boolexpr_e or new_table_e or nil_e had all type of an enumerated type). I one accidently wrote:

expr_of_type(expr, boolexpr_e, newtable_e, nil_e -1)

I forgot the comma between nil_e and -1, because nil_e had an enumerated type, nil_e - 1 was a valid expression and because nil_e was not 0 the given variadic function when trying to get expr_of_type arguments didn't find -1 as last argument and continued searching creating a bug which took me some time to find out.

I don't find the second way nice either, because when adding or removing one more argument from a variadic function you need to change the parameter that contains the number of total arguments.

In searching for a better way to use/create variadic functions I found variadic macros which can solve the bug I had when using the first way. But variadic macros are available to C99 standard. I was looking for a better way to use/create variadic functions in C89. Any ideas?

+5  A: 

In general, you must still pass along the argument count somehow, whether via a sentinel value or via an explicit count.

You could, however, fix your sentinel problem by making a better sentinel. This is one of the reasons why preprocessor macros that expand to negative constants should be surrounded in parentheses:

#define VARARG_SENTINEL (-1)

Then nil_e VARARG_SENTINEL will generate a compilation error.

Using enum or const int would work too:

enum { VARARG_SENTINEL = -1 };

Using a symbolic constant for the sentinel value would be better for other reasons too (more self-documenting, easier to change the underlying value later).

jamesdlin
Using `enum` or `const int` would lead to the same problem when forgetting a comma?
Georg Fritzsche
No, it won't. If you have `const int VARARG_SENTILEL = -1;` when you forget a comma you will have something like this `nil_e VARARG_SENTINEL` which gives a compile error. Same thing happens when you are using `enum { VARARG_SENTINEL = -1 };`.With both solutions when forgetting a comma, it's like having `number1 number2`(e.g. `2 3`) which is an invalid expression but when using -1 as I used to do you will have `number - 1` which is a valid expression.
A: 

There's also always the possibility to avoid completely variadic parameters by using a dynamic structure.

struct vararray {
   uint_t n;
   uint_t params[0];
};

void foo(int tmp, struct varray *pVA);

Can even be complexified with a union of structs of different sizes.

We had once an embedded controller with a specific API where we used this kind of approach, a union of fixed sized struct that was passed to the event handler. It had some advantages as specific types could be used and the compiler could better check the types of function parameters because we should not forget that there is no parameter type checking on variadic functions.

tristopia
A: 

If your compiling C99, you can use variadic macros to provide variable arguments without having to pass the count explicitely:

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

void _foo(size_t n, int xs[])
{
    for(int i=0 ; i < n ; i++ ) {
        int x = xs[i];
        printf("%d\n", x);
    }        
}

#define foo(arg1, ...) do {            \
   int _x[] = { arg1, __VA_ARGS__ };   \
   _foo(sizeof(_x)/sizeof(_x[0]), _x); \
} while(0)

int main()
{
    foo(1, 2, 3, 4);
    foo(-1, -2, -3, -4, -5, -6, -7);
    return 0;
}

Output:

1
2
3
4
-1
-2
-3
-4
-5
-6
-7

This prevents you from returning a value, however. You can return a value with gcc extensions:

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

int _foo(size_t n, int xs[])
{
    int i;
    for(i=0 ; i < n ; i++ ) {
        int x = xs[i];
        printf("%d\n", x);
    }        
    return n;
}

#define foo(arg1, ...) ({              \
   int _x[] = { arg1, __VA_ARGS__ };   \
   _foo(sizeof(_x)/sizeof(_x[0]), _x); \
})

int main()
{
    int x = foo(1, 2, 3, 4);
    printf("foo returned %d\n", x);
    x = foo(-1, -2, -3, -4, -5, -6, -7);
    printf("foo returned %d\n", x);
    return 0;
}

Output:

1
2
3
4
foo returned 4
-1
-2
-3
-4
-5
-6
-7
foo returned 7

But, of course, macros are dead. Long live macros!

EDIT:

Oops, didn't read the OP carefully enough. Sorry!

Tim Schaeffer