问题
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:
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 argumentsAdd 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)
orfoo(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?
回答1:
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).
回答2:
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!
回答3:
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.
来源:https://stackoverflow.com/questions/3272444/use-variadic-functions-in-c89-without-passing-number-of-arguments-or-a-final-arg