Parameter ...

This page was translated by a robot.

In the C and C++ languages, it is possible to ...leave open the exact number of expected arguments and their type in a function's parameter list by using three ellipses. Depending on the situation, more or less arbitrary arguments can be passed to such functions, which are designated as variadic , without the programmer having to program a new signature for every conceivable case. The most famous example of a variadic function is printf:



Declaration


Hello
Hello 42
Hello 42
Hello World
Hello W0r1d
#include <stdio.h>

int printf(const char *, ...);

int main(){
  printf("Hello\n");
  printf("Hello %i\n",         42);
  printf("Hello %i%i\n",       4, 2);
  printf("Hello %s\n",         "World");
  printf("Hello %s%i%c%u%s\n", "W", 0, 'r', 1, "d");
  return 0;
}

This page provides a detailed description of the use of variadic functions and some implementation examples. For a simple listing of the required calls, refer to the description of the stdarglibrary .

Details

A variadic function can expect any number of arguments, but in C and C++ for variadic functions at least one fixed parameter must always be specified in the declaration. The additional parameters are ...specified with three ellipses and must always be declared as the last parameter. The three ellipses are called ellipsis in English . In German, this corresponds to the term ellipse .

Although the possibility of arbitrary arguments is tempting, variadic functions are rarely useful and have the disadvantage that the type of arguments passed cannot generally be checked by the compiler. This type uncertainty can lead to hard-to-detect problems, see below. Usually, however, a programmer knows very well how many and what kind of arguments should be passed to a function, which is why variadic functions are hardly used to this day and are even frowned upon. Nevertheless, there are one or two exceptions that have proven to be practical.

New mechanisms such as templates , function overloading or default values ​​for parameters have been built into C++, which are far more practical and safer than variadic functions. Other languages ​​have built in similar mechanisms or use arrays, for example, to pass any number of (both strict and loosely typed) arguments.

It should be noted that, in addition to variadic functions, there are also variadic macros , which are processed by the preprocessor.

Programming of Variadic Functions

In order to program variadic functions, the stdarglibrary must first be integrated.

C
C++
#include <stdarg.h>
#include <cstdarg>

Some macros are defined in the stdarglibrary, with which the additional arguments can be addressed. In the following example, a function determines the maximum of all passed numbers:



Define list
Initialize list

Get next argument


Close list




441
int max(int count, ...){
  unsigned int curmax = 0;
  va_list argumentlist;
  va_start(argumentlist, count);
  while(count--){
    unsigned int arg = va_arg(argumentlist, unsigned int);
    if(arg > curmax){curmax = arg;}
  }
  va_end(argumentlist);
  return curmax;
}

int main(){
  printf("%i\n", max(5, 42, 365, 441, 253, 133));
  return 0;
}

First a variable with the type va_listis defined. With va_startthen the list is initialized by giving the name of the last parameter which is before the ellipsis .... Then each call to va_argreturns the next argument with a type specification. At the end, the list must be va_endclosed with . These macros are described in more detail in the stdarglibrary .

It should be noted that the type va_listis a type like any other and thus argument lists themselves can be passed to other functions. Here is an example of a variadic function that vsnprintfpasses its argument list to a function.


















1,2,3,4,5
#include <stdio.h>
#include <stdarg.h>

void createliststring(char* buffer, int count, ...){
  char formatstring[1024];
  char* fptr = formatstring;
  count--;
  while(count--){*fptr++='%';*fptr++='i';*fptr++=',';}
  *fptr++='%';*fptr++='i';*fptr++='\0';
  va_list argumentlist;
  va_start(argumentlist, count);
  vsnprintf(buffer, 1024, formatstring, argumentlist);
  va_end(argumentlist);
}

int main(){
  char buffer[1024];
  createliststring(buffer, 5, 1, 2, 3, 4, 5);
  printf("%s\n", buffer);
  return 0;
}

It should be noted that the type va_listcan theoretically also be used as a return type. However, since the argument list is immediately invalidated upon its return, such an approach, with whatever intentions, is pointless and also dangerous.

Type Uncertainty

The additional arguments of variadic functions cannot generally be checked by the compiler for the correct use of a type. On the one hand, this means that within the variadic function, the programmer must assume responsibility for the correct use of types. Addressing a variable with the wrong type can understandably lead to an incorrect result. In the following example, a pointer to a is incorrectly intpassed, but within the function this is interpreted as inthaving no pointer.




-1073743828
void stupidfunction(int dummy, ...){
  va_list argumentlist;
  va_start(argumentlist, dummy);
  printf("%i\n", va_arg(argumentlist, int));
  va_end(argumentlist);
}

int main(){
  int a = 1;
  stupidfunction(0, &a);
  return 0;
}

It is also dangerous to use types that are automatically converted by the compiler using type promotion . In the following example, a is floatpassed, which is automatically converted into a by the compiler double:




BAD_INSTRUCTION
void badfunction(int dummy, ...){
  va_list argumentlist;
  va_start(argumentlist, dummy);
  printf("%f\n", va_arg(argumentlist, float));
  va_end(argumentlist);
}

int main(){
  float a = 1.f;
  badfunction(0, a);
  return 0;
}

Today's compilers, however, warn when such promotions occur. The solution to the floatconversion problem would be to read the additional parameter within the function using and then cast doubleit again as .float


1.000000
float arg = (float)va_arg(argumentlist, double);
printf("%f\n", arg);

A small note on this last example: the attentive reader will notice that printfis also a variadic function. The casting of the value floatis therefore superfluous, since printfa promotion to takes place immediately when is called double. Incidentally, this is the reason why values ​​of type floator can be specified for the function doublewithout additional casting .printf

Special caution is required in this context with the null pointer . Some compilers define this as the integer value 0, which is thus intincluded in the parameter transfer. Understandably, however, this can cause problems on 64-bit systems.

Since no information about types is available to the compiler, the compiler can no longer guarantee const-safety for variadic functions. As a result, a variadic function can unintentionally change values ​​of the calling function:















1

2
void dangerousfunction(int dummy, ...){
  va_list argumentlist;
  va_start(argumentlist, dummy);
  int* arg = va_arg(argumentlist, int*);
  va_end(argumentlist);
  *arg = 2;
}

void printInt(const int* i){
  printf("%i\n", *i);
}

int main(){
  const int a = 1;
  printInt(&a);
  dangerousfunction(0, &a);
  printInt(&a);
  return 0;
}

If the constvariable were also staticdeclared as , this would inevitably lead to a program crash in modern systems, the cause of which may only become clear after days of troubleshooting.

Interesting little tidbit: an older version of the example above used printInta call to instead of directly, printfand at some point the author realized that with newer compilers the result of this nasty example was actually constant, when it shouldn't be. Apparently, compilers constnow do optimizations on variables, even when the compiler is told not to do any optimizations. In concrete terms, the content of the variable awas saved in a register and printfsimply transferred again when it was used again. By calling another function printInt, however, the compiler has to hand over the reins to the basic principles of the language.

Determining the Number of Arguments

In addition to the type uncertainty, variadic functions also have the disadvantage that the total number of arguments passed within the function cannot be determined without further ado. There are different solutions for this. One solution is that the number of arguments passed is passed in one of the first arguments. A parameter (e.g. ) is often simply declared for a variadic function count, which must be specified explicitly when the function is called. Since variadic functions must always have at least one fixed parameter anyway, this solution is very common (see example above).

In the example of printf, the number of expected arguments is determined based on the content of the passed string (today's compilers even printfhave built-in checkers).

There would also be a solution to store the number of expected parameters using a global variable. However, this approach is both frowned upon and dangerous when it comes to doing parallel processing.

Another popular solution to counting the number of arguments passed is to use a sentinel . A sentinel designates that argument from whose position the remaining number of expected arguments is known. Very often the last argument is used as the sentinel. This Sentinel argument is filled with a default value when passed (very common ). If this value occurs within the variadic function when processing the additional arguments, it is clear from this point how many arguments are still available.NULL

Sentinels are not only used for variadic functions, see also the arguments of the mainfunction . However, the use of variadic functions is fairly widespread and so there are even some compilers that can recognize Sentinels, see Attributes .

Duplicate Argument Lists

As long as the additional arguments of a variadic function only have to be addressed once, the macros listed above are sufficient. However, if an argument list is to be processed more than once, there is a macro that copies va_copy(dst,src)the pointer to an argument list from one variable srcto a second variable . dstHere is a simple example of how an argument list is processed multiple times:
















54321
void inverse(int count, ...){
  int i;
  va_list argumentlist1;
  va_list argumentlist2;
  va_start(argumentlist1, count);
  while(count--){
    va_copy(argumentlist2, argumentlist1);
    for(i=0; i<count; i++){va_arg(argumentlist2, int);}
    printf("%i", va_arg(argumentlist2, int));
    va_end(argumentlist2);
  }
  va_end(argumentlist1);
}

int main(){
  inverse(5, 1, 2, 3, 4, 5);
  return 0;
}

Such a duplication is mainly required if a part or even the entire argument list is to be forwarded to another function, or if an argument list is expected as an input parameter of the function to be programmed. At the latest when using the type va_listas a transfer parameter, a programmer may come across inexplicable phenomena in connection with variadic functions. It is then often sufficient to duplicate the argument list.

For a better understanding of how inexplicable phenomena occur or can be avoided, the author recommends considering how variadic functions actually work.

How do Variadic Functions Work?

Variadic functions are possible in C and C++ because these languages ​​allow arguments to be pushed from right to left on the stack . For more information on how stacks are constructed, see the Call Stack page.

The most important thing a programmer needs to know to understand variadic functions is that the argument (of any number ) listed first (leftmost) in the function call operator is always located immediately at the stack pointer within the called function. Since at least one argument must be fixed, you can use the last fixed parameter before the ellipsis at runtime...the position of the additional arguments relative to the stack pointer can be determined. By additionally specifying the expected type of each individual following argument, all arguments can be determined one after the other by working backwards through the stack step by step. This is highly efficient both in terms of runtime and memory space.

The additional arguments of a variadic function can therefore only be reached by specifying the last fixed parameter. From a purely technical point of view, it would be possible in principle to provide a mechanism that could process variadic functions without fixed parameters, but this is not provided for in the scope of the language. There is no special keyword or other special syntactic solution for the first additional argument. Since the implementation can also vary slightly depending on the system, the additional arguments of variadic functions should always be stdargaddressed using the macros defined in the library.

Unfortunately, depending on the system, certain macros are not defined. In general, it is not advisable to simply define standard macros yourself if they do not exist. In fact, the author is not aware of any other solution. Since a programmer will sooner or later stumble over this obstacle, here is the definition of the macro va_copy, as it should probably work on most systems:

#define va_copy(d,s) ((d)=(s))

The other macros should definitely be available (by including the stdarglibrary ). However, to satisfy the curiosity of the reader and the author himself, here is a list of the varargmacros as they could possibly be implemented. The reader is welcome to think through and try out my solution, but it is not recommended to use it.

typedef char* va_list;
#define va_start(v,p) ((v)=(char*)(&(p)))
#define va_arg(v,t)   (((v)+=sizeof(t)),(*(t*)(v)))
#define va_end(v)     ((v)=0)