Thursday, May 23, 2013

C/C++: Variable number of arguments to a function(2)

Summary: This post discuses va_copy and its uses. There is a code below that shows its use.

This is my second post on the topic. If you dont know how these va_* macros work, you may want to read my previous post on this topic. When you learn the concept for the first time, you are tempted to believe that its a great idea to have a lot of functions of this type. Wont that be a great flexibility to have all the time? Unfortunately, thats not true. The problem is:

  1. va_arg() assumes the elements in va_list object to be of a given data type. So, you must know the types in advance to be able to extract the values from the va_ist object.

  2. Once you have traversed a va_list object, you can not get the first element again by resetting the pointers in for the same object.


Lets talk about the possible problems with (2). Lets try out various ways of getting the pointers reset to the beginning of the va_list object. Lets assume we have va_list "src" as the given list which we have traversed and "dest" as a copy of the same list. We want to be able to get the first element of the va_list after src has traversed  the complete va_list object once. On that route, lets first think what a possible implementation of va_list would do. The first thing that comes to mind is that a va_list object could be a pointer to the stack frame of the variadic function. In this situation, it seems perfectly alright to make an assignment say,
va_list dest = src;

Unfortunately, that doesnt work all the time. The next possibility could be that the va_list object is an array of pointers pointing to individual arguments in the variable argumet list , So, we can do a
va_list dest;
*dest = *src;

But that is also not the foolproof way because, on certain systems where arguments are passed in registers, it may be necessary for va_start() to allocate memory, store the arguments there, and also an indication of which argument is next, so that va_arg() can step through the list. Now va_end() can free the allocated memory again. So, there is no way you will be able to access that element again. But all is not gloomy my friend  for the standard thus defines an interface in the name of va_copy() , so that the above assignments can be replaced by

va_list dest;
va_copy(dest, src);
...
va_end(dest);


And this is a foolproof way !!! Finally :)
Note that the compiler wont complain about the wrongdoings in the methods discusses above. The compilation and linking will all pass fine, only when you see the outputs they will appear weird.  The good news is, you can always use va_copy() function to get a replica of a va_list object at that instant of time. So, if you want to use the variable argument list a second time, just use this function. The prototype is:
void va_copy (va_list destination, va_list source);

Just remember that each invocation of va_copy() must be matched by a corresponding invocation of va_end() in the same function. Lets now look at a small code to see va_copy() in action.

[sourcecode language="cpp"]

#include<stdio.h>
#include <stdarg.h>

/* The sum() function can accept variable number of arguments.
In the function declaration "..." at the end 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 );

va_list args_copy; //initialize a copy of va_list to be used
va_copy(args_copy, args); //copy args to args_copy

for (i= 0; i< num_of_arguments; i++ )
/* We add 5 to each element in variable argument list and
print it.
*/
printf("%d ", va_arg (args_copy, int) + 5);

va_end(args_copy); //destruct args_copy

printf("\n");

/**** Next, we use args as in previous post ****/

/* 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 (4,1,2,3,4);
printf("result of sum() with 5 arguement: %d \n", result);

return 0;
}

[/sourcecode]

Play with the above code on ideone. Change a few things here and there and see how it works.

Happy coding !!!

References:

No comments:

Post a Comment