How variable length argument works?
In this article, we will discuss how the variable-length argument works.
Variadic Function: A variadic function are the templates that take a variable-length argument. A variable-length argument is a feature that allows a function to receive any number of arguments. There are situations where a function handles a variable number of arguments according to requirements, such as:
- Sum of given numbers.
- Minimum of given numbers and many more.
A variable number of arguments are represented by three dotes (…).
Minimum value is 6
Calling Convention: Calling convention refers to how a function is called, how parameters are passed, and how the stack is cleaned. C/C++ has a variety of calling conventions but we are only concerned with __cdecl and __stdcall.
Both are very similar but have some differences.
- __cdecl is the default calling convention of C/C++.
- __stdcall is the default calling convention Windows API functions.
Now both calling conventions pass parameters right to left. C/C++ developers choose right to left instead of left to right.
Memory Layout: The memory layout is discussed below:
The only thing that needs attention is the stack and heap segment growing in the opposite direction.
- The heap grows towards a higher address.
- The stack grows towards the lower address. It means higher on the stack is lower in the address. When we push something on the stack, it gets the lowest immediate address in the stack.
Let’s understand this by function:
Inside main() we have a function func(arg1, arg2, arg3)
When func is called then main() is called “caller” and func() is called “callee”
Lets see its stack
caller local variable -> lower on stack higher on address
—- (other stuffs)
callee local variables -> higher on stack lower on address ^ new stack are created here not at top
In the above section, you can clearly see that the first argument gets the lowest address. This is the reason why developers chose right to left instead of left to right because the left to right calling convention will give the first argument the highest address which can cause a problem.
The first argument gets the lowest address and all parameters will have a continuous address in the stack.
Negative Subscript:  is the subscript operator. Below are some of the important points to note about the subscript operator:
- If the subscript operator is operating on a pointer, then its behavior is different. i.e., ptr[x] means *(ptr + x) i.e., value at x*sizeof(data_type_of_pointer) ahead of ptr.
- Similarly, ptr[-x] means *(ptr – x) i.e., value at x*sizeof(data_type_of_pointer) behind ptr.
Below is an example to show how <stdarg.h> works:
Built-in implementation has some restrictions, let’s have a look at them and see how it can be overcome. One of them is there is a need to pass the first argument mandatory, and let’s see why it is mandatory and how to avoid it.
<stdarg.h> uses va_list, va_start, va_arg and va_end. The problem can be broken into 2 parts:
- What do they do?
- How can we replicate it?
va_list: It is a typedef of char* but this is quite different, as when it is used as a type of C-string we get unpredictable results. This is because it is not common typedef. It is built-in defined.
// arg.h typedef char* va_list;
va_start: This is a macro and what it does is to initialize ap, which is actually va_list type (char*) with an address ahead of the first parameter, which is arg1. This is the reason why there is a need to pass the first argument mandatory. It can be any value of any data type but for simplicity, often the number of arguments are passed. It is used to identify the address on the stack where arguments are continuous.
va_arg: This macro is quite complicated. It does two things.
- Returns the required parameter
- Advances to next parameter
#define va_get(ap, type) ((type*)ap)
// casts address held by ap (here arg2) into type*
#define va_advance(ap, type) ap = ap + sizeof(type)
Let’s see what these three dots are. Actually, It is an eclipse operator ( . . . ) and it is C++ defined. This operator is used to pass a variable number of arguments. This is how stdarg.h works, if unable to use the first argument in the variadic function. We can make use of it as
#define va_start(ap, arg1) (ap = (char*)(&arg1))
However, this has no meaning as we don’t know how many arguments are passed, so how can we make use of them?