#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 found a %. The next thing pointed to by fmtPtr is the actual specifier itself, like * d for an interger. We then use va_arg to pop the next arg which was represented by ... */ 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; } } 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. */