Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

An example of use of varargs in C

Here I found an example of how varargs can be used in C.

#include <stdarg.h>  double average(int count, ...) {     va_list ap;     int j;     double tot = 0;     va_start(ap, count); //Requires the last fixed parameter (to get the address)     for(j=0; j<count; j++)         tot+=va_arg(ap, double); //Requires the type to cast to. Increments ap to the next argument.     va_end(ap);     return tot/count; } 

I can understand this example only to some extent.

  1. It is not clear to me why we use va_start(ap, count);. As far as I understand, in this way we set the iterator to its first element. But why it is not set to the beginning by default?

  2. It is not clear to me why we need to give count as an argument. Can't C automatically determine the number of the arguments?

  3. It is not clear to me why we use va_end(ap). What does it change? Does it set the iterator to the end of the list? But is it not set to the end of the list by the loop? Moreover, why do we need it? We do not use ap anymore; why do we want to change it?

like image 405
Roman Avatar asked Apr 03 '13 10:04

Roman


People also ask

How does Varargs work in C?

You call it with a va_list and a type, and it takes value pointed at by the va_list as a value of the given type, then increment the pointer by the size of that pointer. For example, va_arg(argp, int) will return (int) *argp , and increment the pointer, so argp += sizeof int .

What is argument in C with example?

The values that are declared within a function when the function is called are known as an argument. These values are considered as the root of the function that needs the arguments while execution, and it is also known as Actual arguments or Actual Parameters.

What is use of variable number of arguments in C?

To call a function with a variable number of arguments, simply specify any number of arguments in the function call. An example is the printf function from the C run-time library. The function call must include one argument for each type name declared in the parameter list or the list of argument types.


2 Answers

Remember that arguments are passed on the stack. The va_start function contains the "magic" code to initialize the va_list with the correct stack pointer. It must be passed the last named argument in the function declaration or it will not work.

What va_arg does is use this saved stack pointer, and extract the correct amount of bytes for the type provided, and then modify ap so it points to the next argument on the stack.


In reality these functions (va_start, va_arg and va_end) are not actually functions, but implemented as preprocessor macros. The actual implementation also depends on the compiler, as different compilers can have different layout of the stack and how it pushes arguments on the stack.

like image 184
Some programmer dude Avatar answered Sep 19 '22 20:09

Some programmer dude


But why it is not set to the beginning by default?

Maybe because of historical reasons from when compilers weren't smart enough. Maybe because you might have a varargs function prototype which doesn't actually care about the varargs and setting up varargs happens to be expensive on that particular system. Maybe because of more complex operations where you do va_copy or maybe you want to restart working with the arguments multiple times and call va_start multiple times.

The short version is: because the language standard says so.

Second, it is not clear to me why we need to give count as an argument. Can't C++ automatically determine the number of the arguments?

That's not what all that count is. It is the last named argument to the function. va_start needs it to figure out where the varargs are. Most likely this is for historical reasons on old compilers. I can't see why it couldn't be implemented differently today.

As the second part of your question: no, the compiler doesn't know how many arguments were sent to the function. It might not even be in the same compilation unit or even the same program and the compiler doesn't know how the function will be called. Imagine a library with a varargs function like printf. When you compile your libc the compiler doesn't know when and how programs will call printf. On most ABIs (ABI is the conventions for how functions are called, how arguments are passed, etc) there is no way to find out how many arguments a function call got. It's wasteful to include that information in a function call and it's almost never needed. So you need to have a way to tell the varargs function how many arguments it got. Accessing va_arg beyond the number of arguments that were actually passed is undefined behavior.

Then it is not clear to me why do we use va_end(ap). What does it change?

On most architectures va_end doesn't do anything relevant. But there are some architectures with complex argument passing semantics and va_start could even potentially malloc memory then you'd need va_end to free that memory.

The short version here is also: because the language standard says so.

like image 38
Art Avatar answered Sep 20 '22 20:09

Art