[comp.lang.c] using varargs function to call another varargs function

dave@rosesun.Rosemount.COM (Dave Marquardt) (06/29/87)

A user came to me recently with the following problem, and since I've never
written anything using varargs, I'm stumped.

The user wants to pass a variable number of arguments to a function like
printf, like this:

#include <curses.h>

WINDOW	*window1;

dprintf(a,b,s,va_alist)
int	a,b;
char	*s;
va_dcl
{
	wmove(window1, a, b);
	wprintw(window1, s, va_alist);
}

First off, can you do something like this?  Secondly, I doubt that I have
the syntax right.  Can someone point me in the right direction?

	Dave
Dave Marquardt	
dave@rosevax.Rosemount.COM	

chris@mimsy.UUCP (Chris Torek) (07/01/87)

In article <1332@rosevax.Rosemount.COM> dave@rosesun.Rosemount.COM
(Dave Marquardt) writes:
>#include <curses.h>
>
>WINDOW	*window1;
>
>dprintf(a,b,s,va_alist)
>int	a,b;
>char	*s;
>va_dcl
>{
>	wmove(window1, a, b);
>	wprintw(window1, s, va_alist);
>}
>
>First off, can you do something like this?

No.

>Secondly, I doubt that I have the syntax right.

That is close enough.

There are two problems.  First, varargs is (as far as I can tell)
only `kosher' when used for all the arguments.  (The dpANS stdarg
variable-argument-list mechanism works differently, and so I consider
this a minor violation.  Declaring a few fixed arguments seems to
work in all the implementations I have used.)  More important,
though, is that wprintw takes a variable argument list, not a
single argument of type `va_list'.

System V has some of what you need in the form of `vprintf'.  If
curses had a wvprintw, you could do this:

	#include <curses.h>
	#include <varargs.h>

	WINDOW *window1;
	dprintf(va_alist)
		va_dcl
	{
		int a, b;
		char *fmt;
		va_list l;

		va_start(l);
		a = va_arg(l, int);
		b = va_arg(l, int);
		fmt = va_arg(l, char *);
		wmove(window1, a, b);
		wvprintw(window1, fmt, l);
	}

`vprintf' takes a format and an argument of type `va_list'.  vfprintf
takes one additional file pointer, and vsprintf takes a string
pointer.  All return the number of characters printed.  This is
clearly the Right Way to allow this sort of thing; indeed, it
suggests that printf, fprintf, and sprintf themselves be implemented
using <varargs.h> (or <stdarg.h>), and hence portable.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
Domain:	chris@mimsy.umd.edu	Path:	seismo!mimsy!chris

chris@mimsy.UUCP (Chris Torek) (07/01/87)

Oops:  I left a `va_end(l)' out of the source in the parent article,
<7268@mimsy.UUCP>.  For a second example, here is the error()
routine from the local C library.  Note that it `cheats' by declaring
three fixed arguments before the variable argument list.  It also
depends on crt0.o (the thing that calls main()) to set `_argv0'
equal to main's argv[0].  This way all programs that call `error'
print out their own names before printing their error messages.

#include <stdio.h>
#include <varargs.h>

char *_argv0;			/* argv[0], set by C startup code */

/*
 * error - University of Maryland specific (sigh)
 *
 * Useful for printing error messages.  Will print the program name
 * and (optionally) the system error associated with the values in
 * <errno.h>.
 */
error(quit, e, fmt, va_alist)
	int quit;
	register int e;
	char *fmt;
	va_dcl
{
	extern char *sys_errlist[];
	extern int sys_nerr, errno;
	va_list l;
	register char *p = _argv0;

	if (e < 0)
		e = errno;
	(void) fflush(stdout);		/* somewhat gratuitous */
	if (p != NULL)
		(void) fprintf(stderr, "%s: ", p);
/*** the next three lines are the interesting ones ***/
	va_start(l);
	(void) vfprintf(stderr, fmt, l);
	va_end(l);
/******/
	if (e > 0) {
		if (e < sys_nerr)
			(void) fprintf(stderr, ": %s", sys_errlist[e]);
		else
			(void) fprintf(stderr, ": unknown error number %d", e);
	}
	(void) putc('\n', stderr);
	(void) fflush(stderr);
	if (quit)
		exit(quit);
}
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
Domain:	chris@mimsy.umd.edu	Path:	seismo!mimsy!chris

karl@haddock.UUCP (Karl Heuer) (07/02/87)

In article <7268@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes:
>In article <1332@rosevax.Rosemount.COM> dave@rosesun.Rosemount.COM
>(Dave Marquardt) [asks how to write a private variadic function which passes
>its va_alist to some public variadic function such as curses' wprintw].
>
>No [you can't do that].  [The more important problem] is that wprintw takes a
>variable argument list, not a single argument of type `va_list'.
>
>System V has some of what you need in the form of `vprintf'.  If
>curses had a wvprintw, you could [pass the va_alist to it].

Curses *should* have a wvprintw, or vwprintw as it would probably be called.
(It has neither, at least not in the version I have here.)  More generally,
each truly variadic function% should have a corresponding v-function.  I've
never needed a vscanf, but that should exist too, just for completeness.

>This is clearly the Right Way to allow this sort of thing; indeed, it
>suggests that printf, fprintf, and sprintf themselves be implemented using
><varargs.h> (or <stdarg.h>), and hence portable.

Some implementations do exactly that, so that vfprintf is called by each of
the other 5 functions, which are written in C and (except for vsprintf using
undocumented properties of FILE) are portable.  The lowest-level routine,
vfprintf, could be written in C but is often written in assembly for speed.

Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint
%"truly variadic function" is intended to exclude functions with a bounded
number of optional arguments, of which "open" may or may not be an example.

gwyn@brl-smoke.ARPA (Doug Gwyn ) (07/04/87)

/* example implementation of printf() using varargs */

#include	<varargs.h>

printf( va_alist )	/* params: char * followed by variable arguments */
	va_dcl
	{
	va_list	ap;
	char	*s;			/* for the known parameter */

	va_start( ap );
	s = va_arg( ap, char * );	/* pick up first param */
	vprintf( s, ap );		/* specially-designed function */
	va_end( ap );
	}

NOTE: the function being called (vprintf() here) MUST be designed to
accept a va_list parameter.  Also, be very careful to balance va_start()
with va_end(); these macros may have { and } respectively embedded in them,
which affects their proper use in more complex situations.

karl@haddock.UUCP (Karl Heuer) (07/07/87)

In article <6048@brl-smoke.ARPA> gwyn@brl.arpa (Doug Gwyn) writes:
>[example implementation of printf() using varargs]
>... Also, be very careful to balance va_start() with va_end(); these macros
>may have { and } respectively embedded in them, which affects their proper
>use in more complex situations.

I believe Andrew Koenig, the author of varargs, has stated that such
implementations are incorrect; code such as
    va_start(ap);
    if (...) { ... va_end(ap); ... } else { ... va_end(ap); ... }
is perfectly valid.  Is this impossible to do right with some compilers?

Note that ANSI's <stdarg.h>, which replaces varargs, does not mention a
constraint of "syntactic balancing".

Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint

gwyn@brl-smoke.UUCP (07/07/87)

In article <685@haddock.UUCP> karl@haddock.ISC.COM (Karl Heuer) writes:
>Note that ANSI's <stdarg.h>, which replaces varargs, does not mention a
>constraint of "syntactic balancing".

I recommended it for <varargs.h> based on experience.  I don't know if
it is provably necessary for some implementations, but since some of
them (e.g. OSx) depend on it one might as well be careful ("defensive").

hansen@pegasus.UUCP (07/09/87)

The UNIX System V release 3 version of the curses library makes writing
functions such as this much easier by providing the vwprintw() function.
From the Vr3 curses man page:

	vwprintw(win, fmt, varglist)
	    WINDOW *win;
	    char *fmt;
	    va_list varglist;

	This routine corresponds to vfprintf(3S). It performs a
	wprintw() using a variable argument list. The third
	argument is a va_list, a pointer to a list of arguments,
	as defined in <varargs.h>. See the vprintf(3S) and
	varargs(5) manual pages for a detailed description on
	how to use variable argument lists.

Your dprintf function now becomes:

	#include <curses.h>
	#include <varargs.h>
	
	WINDOW	*window1;
	
	dprintf(va_alist)
	va_dcl
	{
		int	a,b;
		char	*s;
		va_list ap;
		va_start(ap);
		a = va_arg(ap, int);
		b = va_arg(ap, int);
		s = va_arg(ap, char*);
		wmove(window1, a, b);
		vwprintw(window1, s, va_alist);
		va_end(ap);
	}

(Note how the arguments are pulled off the stack rather than being declared
as parameters. The way you had it is non-portable using <varargs.h>. See the
varargs man page for more info on using varargs.)

There is also a corresponding vwscanw() function for doing input.

If you don't have the System V release 3 version of curses, you should look
into using the vsprintf() function which has been mandated as part of the
dpANS C standard. Using this function, you can replace the above vwprintw()
statement with:

	char buf[BUFSIZ];
	vsprintf(buf, s, va_alist);
	wprintw(window1, "%s", buf);

If you don't have vsprintf(), complain to your C vendor. There have been
several public domain versions posted in the past.

					Tony Hansen
					ihnp4!pegasus!hansen