[comp.lang.c] Can ANYONE tell me why this code snippet doesn't work??

rickf@pnet02.cts.com (Rick Flower) (10/15/88)

I've been trying to get the following code to work.. I've tried just about
everything that I could think of (along the lines of modifications).  All I'm
trying to do is write a different printf routine to route information through
a window text printing routine...  If anyone can help me, let me know..

-----------------------------------------------------------------------------
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..

Thanks in advance..

===============================================================================
                                I Thought So...

UUCP: {ames!elroy, <backbone>}!gryphon!pnet02!rickf
INET: rickf@pnet02.cts.com
===============================================================================

chris@mimsy.UUCP (Chris Torek) (10/16/88)

In article <7778@gryphon.CTS.COM> rickf@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@mimsy.umd.edu	Path:	uunet!mimsy!chris

vch@attibr.UUCP (Vincent C. Hatem) (10/18/88)

In article <7778@gryphon.CTS.COM%, rickf@pnet02.cts.com (Rick Flower) writes:
% I've been trying to get the following code to work.. I've tried just about
% everything that I could think of (along the lines of modifications).  All I'm
% trying to do is write a different printf routine to route information through
% a window text printing routine...  If anyone can help me, let me know..
% 
% -----------------------------------------------------------------------------
% 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);
% }

You aren't passing a POINTER to the arguments, as you seem to think you are.

this should be (if I'm not mistaken... i haven't tried this lately)

unsigned *args;
         sprintf(buff,fmt,&args);
                          ^
                          !
-Vince

-- 
Vincent C. Hatem                            | att ---->\ (available from any
AT&T International                          | ulysses ->\ Action Central site)
International Operations Technical Support  | bellcore ->\   
1200 Mt Kemble Ave, Basking Ridge, NJ 07920 | ihnp4 ----->\__ !attibr!vch  

thomas@uplog.se (Thomas Hameenaho) (10/18/88)

In article <7778@gryphon.CTS.COM> rickf@pnet02.cts.com (Rick Flower) writes:
>I've been trying to get the following code to work.. I've tried just about
>everything that I could think of (along the lines of modifications).  All I'm
>trying to do is write a different printf routine to route information through
>a window text printing routine...  If anyone can help me, let me know..
>
>-----------------------------------------------------------------------------
>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);
>}
>
>------------------------------------------------------------------------------

The problem is that the stack doesn't look the way you would expect on entry
to sprintf().

For a normal 68k machine the stack layout would be:

	+---------------+
fp ->	| frame pointer |--+
	+---------------+  |
	| ret to Test	|  |
	+---------------+  |
	| ptr to buff	|  |
	+---------------+  |
	| ptr to fmt str|  |
	+---------------+  |
	|	10	|  |
	+---------------+  |
	|		|  |
	.  buff (132)	.  |
	|		|  |
	+---------------+  |
     +--| frame pointer |<-+
     |  +---------------+
     |  | ret to main   |
     |  +---------------+
     |  | ptr to fmt str|
     |  +---------------+
     |  |       10      |
     |  +---------------+
     |  |       20      |
     |  +---------------+
     |  |       30      |
     |  +---------------+
     |  |       40      |
     |  +---------------+
     +->| frame pointer |
        +---------------+
        | ret from main |
        +---------------+

As you can see the arguments to sprintf are just the three first.
If you want to do something like what I think you are aiming at you have to
do it some other way. Possibly manipulating the stackpointer in assembler!
-- 
Real life:	Thomas Hameenaho		Email:	thomas@uplog.{se,uucp}
Snail mail:	TeleLOGIC Uppsala AB		Phone:	+46 18 189406
		Box 1218			Fax:	+46 18 132039
		S - 751 42 Uppsala, Sweden

mikpe@mina.liu.se (Mikael Pettersson) (10/19/88)

In article <7778@gryphon.CTS.COM> rickf@pnet02.cts.com (Rick Flower) writes:
>I've been trying to get the following code to work..
> [Rick wants to write his own printf(3s) clone]
> [buggy def of Test() omitted]
>main()
>{
>        Test("%d %d %d %d",10,20,30,40);
>}

The answer to your problem is simple: use varargs().
With varargs, you can either parse the format string just like printf(3s)
does, or you can be a little lazy and let vsprintf(3s) do the formatting
for you before sending the result to whoever wants it. I'll give you an
example:
---snip-snip-snip---
#include <stdio.h>
#include <varargs.h>

void Test(va_alist)
va_dcl					/* n.b. NO ; */
{
	va_list ap;
	char *fmt, buf[BUFSIZ];

	va_start(ap);			/* init */
	fmt = va_arg(ap, char *);	/* get 1st arg: a char * */
	vsprintf(buf, fmt, ap);		/* do the formatting */
	fputs(buf, stdio);		/* dump it on stdout */
	va_end(ap);			/* cleanup */
}
---snip-snip-snip---

Check the man pages for more detail. Any decent C book (like H&S-2) will
probably also have some examples.

In <315@uplog.se> thomas@uplog.UUCP (Thomas Hameenaho) responds:
>The problem is that the stack doesn't look the way you would expect on entry
>to sprintf().
>
> [ lots of machine AND compiler specific detail omitted ]
>...
>If you want to do something like what I think you are aiming at you have to
>do it some other way. Possibly manipulating the stackpointer in assembler!

Noooo!
The great advantage in using varargs(3) is PORTABILITY. It's guaranteed
to work in any conforming ANSI C implementation (where it's called <stdarg.h>)
and it will work in virtually all existing UNIX C compilers as well.
Assumptions about stack layout, parameter passing and order of evaluation
will only lead to bugprone and non-portable code.

/Mike
-- 
Mikael Pettersson           ! Internet:mpe@ida.liu.se
Dept of Comp & Info Science ! UUCP:    mpe@liuida.uucp  -or-
University of Linkoping     !          {mcvax,munnari,uunet}!enea!liuida!mpe
Sweden                      ! ARPA:    mpe%ida.liu.se@uunet.uu.net

thomas@uplog.se (Thomas Hameenaho) (10/19/88)

In article <982@mina.liu.se> mikpe@mina.liu.se (Mikael Pettersson) writes:
>Noooo!
>The great advantage in using varargs(3) is PORTABILITY. It's guaranteed
>to work in any conforming ANSI C implementation (where it's called <stdarg.h>)
>and it will work in virtually all existing UNIX C compilers as well.
>Assumptions about stack layout, parameter passing and order of evaluation
>will only lead to bugprone and non-portable code.

I agree fully with you. However not THAT many systems have v{sf}printf()
(BSD systems doesn't, at least not 4.3) and my response was an attempt 
to explain what was wrong with the code.
I would definately NOT try to manipulate the sp.
-- 
Real life:	Thomas Hameenaho		Email:	thomas@uplog.{se,uucp}
Snail mail:	TeleLOGIC Uppsala AB		Phone:	+46 18 189406
		Box 1218			Fax:	+46 18 132039
		S - 751 42 Uppsala, Sweden

chris@mimsy.UUCP (Chris Torek) (10/21/88)

>In article <982@mina.liu.se> mikpe@mina.liu.se (Mikael Pettersson) writes:
>>The great advantage in using varargs(3) is PORTABILITY. ...

In article <316@uplog.se> thomas@uplog.se (Thomas Hameenaho) writes:
>I agree fully with you. However not THAT many systems have v{sf}printf()
>(BSD systems doesn't, at least not 4.3) and my response was an attempt 
>to explain what was wrong with the code.

4.3BSD-tahoe does have v*printf.  Here they are.  Install in
src/lib/libc/stdio.  Note that vsprintf has the same bug that
sprintf has (it will scribble on a random fd if you print more
than 32767 characters).
---------------------------------vsprintf------------------------------
#include <stdio.h>
#include <varargs.h>

int
vsprintf(str, fmt, ap)
	char *str, *fmt;
	va_list ap;
{
	FILE f;
	int len;

	f._flag = _IOWRT+_IOSTRG;
	f._ptr = str;
	f._cnt = 32767;
	len = _doprnt(fmt, ap, &f);
	*f._ptr = 0;
	return (len);
}
---------------------------------vfprintf------------------------------
#include <stdio.h>
#include <varargs.h>

int
vfprintf(iop, fmt, ap)
	FILE *iop;
	char *fmt;
	va_list ap;
{
	int len;

	len = _doprnt(fmt, ap, &f);
	return (ferror(iop) ? EOF : len);
}
---------------------------------vprintf-------------------------------
#include <stdio.h>
#include <varargs.h>

int
vprintf(fmt, ap)
	char *fmt;
	va_list ap;
{
	int len;

	len = _doprnt(fmt, ap, stdout);
	return (ferror(stdout) ? EOF : len);
}
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

henry@utzoo.uucp (Henry Spencer) (10/23/88)

In article <982@mina.liu.se> mikpe@mina.liu.se (Mikael Pettersson) writes:
>The great advantage in using varargs(3) is PORTABILITY. It's guaranteed
>to work in any conforming ANSI C implementation (where it's called <stdarg.h>)

Beware:  varargs.h and stdarg.h are not quite the same.  Unfortunately.
-- 
The meek can have the Earth;    |    Henry Spencer at U of Toronto Zoology
the rest of us have other plans.|uunet!attcan!utzoo!henry henry@zoo.toronto.edu