Passing Variable Numbers of Arguments

Chris Torek torek at elf.ee.lbl.gov
Thu Feb 14 22:07:53 AEST 1991


In article <5196 at media-lab.MEDIA.MIT.EDU> dyoung at media-lab.media.mit.edu.UUCP
(David Young) writes:
>What I'd like is something that could transform a call like:
>
>     PringMsg( window, formatString, <formatArgs>)
>
>into the following chunk of code:
>
>     { 
>       sprintf( globalFoo, formatString, <formatArgs>);
>       BlahBlah( window, globalFoo);
>     }

The following is the only current approach that is anywhere near
portable:

	#define SIZE	1024	/* and pray */
	#if __STDC__
	void
	PrintMsg(WINDOW *window, char *fmt, ...) {
		va_list ap;
		char buf[SIZE];

		va_start(ap, fmt);
		(void) vsprintf(buf, fmt, ap);
		va_end(ap);
		BlahBlah(window, buf);
	}
	#else
	void
	PrintMsg(va_alist)
		va_dcl
	{
		WINDOW *window;
		char *fmt;
		va_list ap;
		char buf[SIZE];

		va_start(ap);
		window = va_arg(ap, WINDOW *);
		fmt = va_arg(ap, char *);
		(void) vsprintf(buf, fmt, ap);
		va_end(ap);
		BlahBlah(window, buf);
	}
	#endif

(Something very much like this, but with a size of 2048, appears in
the X11 sources.)

This method is not terribly satisfactory.  The size sets an upper bound
on the amount that can be printed in one call.  Worse, if the format
plus arguments produce more than SIZE-1 characters, vsprintf silently
overruns the buffer, typically causing some kind of catastrophic error
soon afterward.

The latest Berkeley system (i.e., the one you cannot get yet) has two
new facilities in the C library that improve on this.  The first is the
pair of functions `snprintf' and `vsnprintf', which take a buffer size.
They return the number of characters required to hold the entire output.
Thus:

	/* declarations and beginning as before, but add `char *cp;'
	   and `int ret;' */
	ret = vsnprintf(buf, sizeof buf, fmt, ap);
	if (ret < sizeof buf) {
		/* everything was printed; the buffer is fine */
		cp = buf;
		goto done;	/* XXX `goto' is required on Pyramid */
	}
	/* some of the text was truncated */
	va_end(ap);		/* this macro may include an unbalanced } */
	cp = malloc(ret + 1);
	if (cp == NULL)
		die("out of memory");
	va_start(ap);
	window = va_arg(ap, WINDOW *);
	fmt = va_arg(ap, char *);
	(void) vsnprintf(cp, ret + 1, fmt, ap);
done:
	va_end(ap);
	BlahBlah(cp);
	if (cp != buf)
		free(cp);


The second facility, and the one that is preferred for this sort of
thing, is the ability to open your own I/O functions.  In this case the
desired function is the equivalent of the `write' system call, and it
needs one pointer parameter, which happens to be exactly what the
interface provides (in the shape of a `void *'):

	/* __STDC__ assumed here */
	static int
	aux_write(void *cookie, const char *buf, int nbytes) {
		WINDOW *w = cookie;

		window_write(w, buf, nbytes);
		return nbytes;
	}

	int
	PrintMsg(WINDOW *w, const char *fmt, ...) {
		FILE *fp;
		va_list ap;

		fp = fwopen(w, aux_write);
		if (fp == NULL)
			die("out of memory, or something equally bad");
		va_start(ap, fmt);
		(void) vfprintf(fp, fmt, ap);
		va_end(ap);
		(void) fclose(fp);
	}

This allows an arbitrarily large amount of data to move between the
caller and the window, passing through an arbitrarily small pipe
(the stdio buffer attached to the file `fp').

The only requirement here is that user I/O functions act like read()
and write() (and, if seek and close are provided, lseek() and close()).
-- 
In-Real-Life: Chris Torek, Lawrence Berkeley Lab EE div (+1 415 486 5427)
Berkeley, CA		Domain:	torek at ee.lbl.gov



More information about the Comp.lang.c mailing list