Bharat Banate's Work Profile

View Bharat Banate's profile on LinkedIn

Wednesday, September 5, 2007

C/C++: Variable-length argument lists

Let's move back to basics! The C programming...We know C programming very well. Do we? Well, we have learnt almost everything about C programming. But not everything.
C supports variable-length argument lists for functions. Best and well-known examples are printf and scanf functions and all their variants. But had we written any variable-argument function?

This post contains an implementation of a minimal version of printf, to show how to write a function that processes a var-arg list in a portable way. As we are mainly interested in the argument processing, min_printf will process the format string and arguments but will call original printf to do the format conversions.
The proper declaration for printf is:
int printf(char *fmt,...)
where, the declaration ... means that number and types of these arguments may vary.
The declaration ... can only appear at the end of an argument list.
Our
min_printf is declared as:
void min_printf(char *fmt,...)

Since we will not return the character count that printf does, return type is void. The real crux is how min_printf walks along the argument list when the list even doesn't have a name. The standard header stdarg.h contains a set of macro definitions that define how to step through an argument list. The implementation of this header will vary from machine to machine, but the interface it presents is uniform.
The type va_list is used to declare a variable that will refer to each argument in turn. In our function min_printf, this variable is ap, for "argument pointer". The macro va_start initializes ap to point to the first unnamed argument. It must be called once before ap is used. There must be at least one named argument. The final named argument is used by va_start to get started. Each call to va_arg returns one argument and steps ap to the next one. va_arg uses a type name to determine what type to return and how big a step to take. Finally, va_end does whatever cleanup is necessary. It must be called before the function returns. These properties form the basis for our simple min_printf. Here is the code for min_printf:
/* min_printf: minimal printf with variable argument list */
void min_printf(char *fmt, ...)
{
_va_list ap; /* points to each unnamed arg */
_char *p, *sval;
_int ival;
_double dval;
_/* make ap point to 1st unnamed arg */
_va_start(ap,fmt);
_for (p=fmt; *p; p++)
_{
__if (*p != '%')
__{
___putchar(*p);
___continue;
__}//if
__switch(*++p)
__{
___case 'd':
____ival = va_arg(ap,int);
____printf("%d", ival);
____break;
___case 'f':
____dval = va_arg(ap,double);
____printf("%f", dval);
____break;
___case 's':
____for (sval=va_arg(ap,char*); *sval; sval++)
_____putchar(*sval);
___break;
___default:
____putchar(*p);
____break;
__}//switch
_}//for
_/* clean up when done */
_va_end(ap);
}//min_printf
Note:
If you copy-paste this code, kindly remove the underscores( _ ) at the beginning of each line. Underscores are added for indentation purpose.

0 comments: