I made a small C library that implements graph theory algorithms and binds them for use in Python.
I send it to a friend to check it and he told me that va_list
is "dangerous" and must not be used in this kind of project.
So the question is. In which cases va_list
should be used?
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.
The va_list may be passed as an argument to another function, but calling va_arg() within that function causes the va_list to have an indeterminate value in the calling function. As a result, attempting to read variable arguments without reinitializing the va_list can have unexpected behavior.
h header file to extract the values stored in the variable argument list--va_start, which initializes the list, va_arg, which returns the next argument in the list, and va_end, which cleans up the variable argument list.
h . The type va_list is used for argument pointer variables.
The main problem I see is that there's no guarantee that you really got the number of arguments that you were expecting, and no way to check for that. This makes errors undetectable, and undetectable errors are, obviously, the most dangerous kind. va_arg
is also not type-safe, which means that if you pass a double
and expect an unsigned long long
, you'll get garbage instead of a good-looking integer, and no way to detect it at compile-time. (It becomes much more of a mess when the types don't even have the same size).
Depending on the data you deal with, this may be more or less of a problem. If you pass pointers, it becomes almost instantly fatal to omit an argument because your function will retrieve garbage instead, and this could (if the planets are properly aligned) become a vulnerability.
If you pass "regular" numeric data, it then depends on if the function is critical. In some cases you can easily detect an error looking at the function's output, and in some practical cases it really isn't that much of a problem if the function fails.
It all revolves about if you're afraid of forgetting arguments yourself, actually.
C++11 has a variadic template feature that allows you to treat an arbitrary number of parameters in a safe way. If the step from C to C++ isn't hurting too much, you could look into it.
In C++11, va_list
should never be used, as it provides better alternative called variadic template, which is typesafe whereas va_list
is not.
In C, you could use va_list
when you need variadic function, but be careful, as it is not typesafe.
And yes, your friend is correct: va_list
is dangerous. Avoid it as much as possible.
In C and C++03, the standard library function printf
is implemented using va_list
, that is why C++03 programmers usually avoid using this, for it is not typesafe.
But a variadic typesafe printf
could be implemented in C++11, as: (taken from wiki)
void printf(const char *s)
{
while (*s) {
if (*s == '%' && *(++s) != '%')
throw std::runtime_error("invalid format string: missing arguments");
std::cout << *s++;
}
}
template<typename T, typename... Args>
void printf(const char *s, T value, Args... args)
{
while (*s) {
if (*s == '%' && *(++s) != '%') {
std::cout << value;
++s;
printf(s, args...);
return;
}
std::cout << *s++;
}
throw std::logic_error("extra arguments provided to printf");
}
va_list
has some disadvanges that are related to the underspecification of the function arguments:
int
are promoted, all float
are
promoted to double
. In some border case you'd not received what you
wanted in the called function.If you pass in the number of arguments anyhow and these are of the same known type you could just pass them in with a temporary array, written for C99:
void add_vertices(graph G, vertex v, size_t n, vertex neigh[n]);
you would call this something like that
add_vertices(G, v, nv, (vertex []){ 3, 5, 6, 7 });
If that calling convention looks too ugly to you, you could wrap it in a macro
#define ADD_VERTICES(G, V, NV, ... ) add_vertices((G), (V), (NV), (vertex [NV]){ __VA_ARG__ })
ADD_VERTICES(G, v, nv, 3, 5, 6, 7);
here the ...
indicates a similar concept for macros. But the result is much safer since the compiler can do a type check and this is not delayed to the execution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With