Can ANYONE tell me why this code snippet doesn't work??

Chris Torek chris at mimsy.UUCP
Sun Oct 16 20:41:29 AEST 1988


In article <7778 at gryphon.CTS.COM> rickf at pnet02.cts.com (Rick Flower) writes:
>void Test(fmt,args)
>char *fmt;
>unsigned *args;
>{
>char buff[129];
>
>        sprintf(buff,fmt,args);
>        printf("%s\n",buff);
>}
>
>main()
>{
>        Test("%d %d %d %d",10,20,30,40);
>}

>        I would think that it would just insert the 10 and 20 and 30 and
>40 in the appropriate places in the fmt line.. but it only does the 1st two
>numbers..  the other two are garbage values..

It is amazing that you get even the first *two* numbers (unless perhaps
you are on a 16-bit-int 32-bit-pointer machine, such as a PDP-11 or IBM
PC using large model).

*Anyone* should be able to give you at least two reasons why it does
not work: imprimis, you told sprintf to print four `int's (via %d), yet
you passed it one pointer to unsigned; and secundus, Test() takes two
arguments, the second being type `unsigned *', yet you passed five, the
second being type `int'.  You may not arbitrarily ignore either the
types of arguments or the number of arguments to any function, save in
one case: if the function being called is variadic.

[Now that you have been thoroughly chastised :-) ...]

The question now is how to write it so that it *will* work.  Since what
you want is a variadic function that acts much like printf, you must
first declare a variadic function.  There are two `standard' ways to do
this, one the de facto standard, the other from the draft proposed ANSI
standard for C.  The two are not only different, they are also quite
thoroughly incompatible---a mistake, as this was not really necessary
(despite the cries of anguish from certain compiler-writers).

	Method 1.  De facto standard.

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

	int
	my_printf(va_alist)
		va_dcl		/* N.B.: no semicolon */
	{
		int ret;
		char *fmt;
		va_list ap;
		char buf[129];	/* rather small, but might perhaps suffice */

		va_start(ap);
		fmt = va_arg(ap, char *);
--->		ret = <some_call>(buf, fmt, <some_args>);
		va_end(ap);
		<do something with buf>
		return (ret);
	}

Everything except the line marked by the arrow is setup mechanics.  The
line with the arrow is the key to invoking *printf properly.  So what
goes in the <call> and <args> fields?

		ret = vsprintf(buf, fmt, ap);

vsprintf is a version of sprintf that takes a `va_list' rather than
an actual parameter list.  It should now be obvious that sprintf itself
could be written as

	int
	sprintf(va_alist)
		va_dcl
	{
		int ret;
		char *fmt, *buf;
		va_list ap;

		va_start(ap);
		buf = va_arg(ap, char *);
		fmt = va_arg(ap, char *);
		ret = vsprintf(buf, fmt, ap);
		va_end(ap);
		return (ret);
	}

There are three assumptions about your system here, and one obvious
one.  The obvious one is that you have <varargs.h> and can use the de
facto standard varargs mechanism.  The other assumptions are that you
have vsprintf, and that you have a post-V7/4.3BSD sprintf that returns
`int' rather than `char *'.

	Method 2: The dpANS standard.

Method 2 is just like method one except that some of the names are
changed, and some arguments are permitted and others required:

	#include <stdio.h>
	#include <stdarg.h>

	int
	my_printf(char *fmt, ...) /* the `...'s are part of the syntax */
	{
		int ret;
		va_list ap;
		char buf[128];	/* same comment as before */

		va_start(ap, fmt);
		ret = vsprintf(buf, fmt, ap);
		va_end(ap);
		<do something with buf>
		return (ret);
	}

The `fmt' argument is both permitted and required: `...' may only appear
after `,', and va_start must name the same parameter that appeared just
before the `...'.  If there were more fixed parameters, those, too,
could be listed (so long as the va_start names the one closest to the
three dots).
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris at mimsy.umd.edu	Path:	uunet!mimsy!chris



More information about the Comp.lang.c mailing list