Functions With Variable Number Of Arguments In C
#include <stdio.h>
#include <stdarg.h>
#define TARGET_SIZE 80
#define myType myFloat /* myDouble or myFloat: Illustrates promotion */
#define type float /* double or float: Illustrates promotion */
void printp(char *fmt, ...);
int main(void)
{
char *myDrink = "Pepsi Cola";
int times = 24;
type myType = 2.5;
printp("I drink %d cans of %s in %f hours.\n", times, myDrink, myType);
return 0;
}
void printp(char *fmt, ...)
{
va_list arg_ptr;
type myType;
int myInt;
char *fmtPtr, *myString;
char target[TARGET_SIZE]; /* The string to eventually print */
char *targetPtr = target; /* Pointer to that string */
/* Initialize the process. The 2nd argument should be the last arg passed to this function of
* which we're sure about the number and type of. Here, it'll be fmt -- the format string.
* Think of the 1st arg to printf. Here on in, arg_ptr will be pointing to our optional args.
*/
va_start(arg_ptr, fmt);
/* Process fmt, char by char. If it's not a signal for a conversion specifier, we print it.
*/
for (fmtPtr=fmt; *fmtPtr; ++fmtPtr) {
if (*fmtPtr != '%') {
targetPtr += sprintf(targetPtr, "%c", *fmtPtr);
continue;
}
/* We just found a %. The next thing pointed to by fmtPtr is the actual specifier itself,
* like s for a string or d for an interger. We then use va_arg to pop the next arg which
* was represented by the ... arg.
*/
switch(*++fmtPtr) {
case 's':
myString = va_arg(arg_ptr, char *);
targetPtr += sprintf(targetPtr, "%s", myString);
break;
case 'd':
myInt = va_arg(arg_ptr, int);
targetPtr += sprintf(targetPtr, "%d", myInt);
break;
case 'f':
myType = va_arg(arg_ptr, type);
targetPtr += sprintf(targetPtr, "%f", myType);
break;
}
}/*endfor*/
va_end(arg_ptr);
printf("%s", target);
}
/* Exercise to the reader:
*
* 1. Implement a character specifier, %c, in printp().
* 2. You currently cannot print a percent sign (why?). Implement it.
* 3. In the integer case, what am I doing with the return value of sprintf?
* 4. Find the buffer overflow (simple) and fix it (tedious but important).
*/
/* Nota Bene:
*
* Some data types get widened, that is, arg_ptr gets interpreted as a pointer to a wider data
* type, ending on a 2, 4 or 8 byte boundary. This means trouble when you pass a char and cast
* it as a char, and when pass a float and cast it as a float. You can pass chars and floats;
* you just can't receive them. Instead, you should interpret them as ints and doubles. gcc
* actually handles char correctly, but this isn't portable. I put in the type/myType defines
* defines to illustrate this point.
*/
Prev: Sample code
Next: Details