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