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 arguments -
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)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?
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:
Then
nil_e VARARG_SENTINELwill generate a compilation error.Using
enumorconst intwould work too: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).