Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In which cases va_list should be used

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?

like image 477
kechap Avatar asked Dec 08 '11 17:12

kechap


People also ask

What is the use of va_list?

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.

Can you pass va_list to another function?

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.

Which header file contains va_list?

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.

Is va_list a pointer?

h . The type va_list is used for argument pointer variables.


3 Answers

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.

like image 162
zneak Avatar answered Nov 13 '22 19:11

zneak


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");
}
like image 44
Nawaz Avatar answered Nov 13 '22 18:11

Nawaz


va_list has some disadvanges that are related to the underspecification of the function arguments:

  • when calling such a function the compiler doesn't know what types of arguments are expected, so the standard imposes some "usual conversion" before the arguments are passed to the function. E.g all integers that are narrower than int are promoted, all float are promoted to double. In some border case you'd not received what you wanted in the called function.
  • In the called function you tell the compiler what type of argument you expect and how much of them. There is no guarantee that a caller get's it right.

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.

like image 45
Jens Gustedt Avatar answered Nov 13 '22 19:11

Jens Gustedt