Saturday, May 18, 2013

C/C++ - Variable number of arguments to a function

Ever wondered how printf() in C would be implemented ? If you are a C-lover, I guess you would have, for its a function that looks pretty different from the other library functions, consumes tons of options and its very powerful. If you are not a C-fan and do not know much of it, you may want to have a look here . Well, one of the great flexibility of printf() comes from the fact that it can consume variable number of arguments. If you are scratching your head about how it does that, hold on,  you will know it in a minute. That is what this post aims at :) . Lets look at some examples to understand what 'variable number of arguments' means.

[sourcecode language="c"]

#include<stdio.h>
int main()
{
char * string = "with";
printf("printf with 1 arguement\n");
printf("Printf with %d arguements\n", 2);
printf("Printf %s %d arguements", string, 3);
return 0;
}
[/sourcecode]

Note that the first version takes one argument, the second version takes two arguments and the third takes three. Have you ever written a function definition in C that can be invoked with any number of arguements 1...N ? If not, lets write one. But on the way we need to learn a couple of things as ingredients to it. We need to know of four simple things. The definitions have been taken from cplusplus.com. If you dont understand what they mean, you may first want to see how they are used ( see the code below) and believe me, its very simple to understand them from the usage.

  1. va_list:  Type to hold information about variable arguments.
     Objects of this type can only be used as argument for the  va_start,  va_arg,  va_end and  va_copy macros, or functions that use them, like the variable argument functions in <cstdio>  (vprintf,  vscanf,  vsnprintfvsprintf and  vsscanf).


  2. void va_start (va_list ap, paramN)

    Initialize a variable argument list. Initializes ap to point to the first unnamed argument. It retrieve the additional arguments after parameter paramN. A function that invokes va_start(), should also invoke va_end() before it returns.


  3. type va_arg (va_list ap, type)

    Retrieve next argument. This macro expands to an expression of type type with the value of the current argument in the variable arguments list identified by ap.


  4. void va_end (va_list ap);

    Each invocation of va_start() must be matched by a corresponding invocation of va_end() in the same function.. It performs the appropriate actions to facilitate a normal return by a function that has used the va_list object ap .


Pheww thats a lot of technical Jargon !! Lets revert to some simpler words now. In short, you need to first define a list of type va_list by saying something like.. va_list args. Next, invoke a call to va_start() to indicate where the variable list of arguments  starts from, extract each argument from the variable list (args in our case) using va_arg() and finally dont forget calling va_end().

Note that apart from va_ag(), a va_list object (named ap in declarations below ) can also be consumed by versions of printf() that are preceded by 'v', for example:

  1. int vprintf ( const char * format, va_list ap ); - Prints formatted data from variable argument list to stdout,

  2. int vfprintf ( FILE * stream, const char * format, va_list ap ); - Writes formatted data from variable argument list to stream and

  3. int vsprintf (char * s, const char * format, va_list ap ); - Writes formatted data from variable argument list to string s.


Lets look at an example that uses the a va_arg(). For the above mentioned printf versions, you can find a C program for each of the 3 functions listed above in the links in the "References" section  (#2, #3, #4 respectively) at the end of this post.

[sourcecode language="c"]
#include<stdio.h>
#include <stdarg.h>

/* The sum() function can accept variable number of arguments.
In the function declaration ... means that the number and
type of the arguments may vary. The marker ... can only
appear at the end.
*/
int sum(int num_of_arguments, ... )
{
/* A list to store the aruements. In example, this may contain
1,2,3 and 4 elements on successive calls from main()
*/
va_list args;
int sum= 0, i;

/* Directing the va_list args initialized above to start storing
all parameters folloowing the first parameter 'num_of_arguments'
*/
va_start (args, num_of_arguments );

/* Loop until all parameters are seen. */
for (i= 0; i< num_of_arguments; i++ )
/* Extract the next value in argument list and add it to sum. */
sum += va_arg (args,int);

/* Signal that we are done with our usage of the list */
va_end (args);

return sum;  /* Returns the calculated sum.*/
}
int main()
{
int result;

result=sum (1,1);
printf("result of sum() with 2 arguement: %d \n", result);

result=sum (2,1,2);
printf("result of sum() with 3 arguement: %d \n", result);

result=sum (3,1,2,3);
printf("result of sum() with 4 arguement: %d \n", result);

result=sum (4,1,2,3,4);
printf("result of sum() with 5 arguement: %d \n", result);

return 0;
}
[/sourcecode]

Time to see what the function outputs. Here you go:
result of sum() with 2 arguement: 1 
result of sum() with 3 arguement: 3
result of sum() with 4 arguement: 6
result of sum() with 5 arguement: 10

And as always play with the above  code on ideone. Change a few things here and there and see how it works. Done with this? Lets try and implement our own version of printf().

References:

  1. Linux man page: http://linux.die.net/man/3/va_arg

  2. http://www.cplusplus.com/reference/cstdio/vprintf/

  3. http://www.cplusplus.com/reference/cstdio/vfprintf/

  4. http://www.cplusplus.com/reference/cstdio/vsprintf/

2 comments: