[net.lang.c] gripe: variable arg lists

robert@gitpyr.UUCP (Robert Viduya) (11/14/84)

[]

This has probably been mentioned before, but I'd  like  to  present  a  new
aspect of the old problem of variable arguments.

About a day ago, a program  arrived  at  our  site  via  net.sources.   The
program  was a wargame simulator known as 'ogre'.  I pulled the program off
and compiled and attempted to run it.  The program repeatedly died  in  two
routines:  display()  and display_xy().  For the sake of brevity, and since
the problems in both routines were identical, I'll only show display():

display(line, format, args)
int line;
char *format;
int args;
{

    movecur(line, 0);
    eeol();
    _doprnt(format, &args, stdout);

}

The following are examples of how the author called display():

        display(16, "%s infantry (%d/%d D%d M%d)", action,
            unit[i].attack, unit[i].range,
            unit[i].defend, unit[i].moves_left);

        display(16, "left to place: %d armor, %d infantry%s",
            armor_points, infantry_points,
            (cp_set) ? "." : ", CP");

Note the author's unconventional use of  _doprnt().   The  program's  exact
point of death was in _vaarg().

Obviously, the author  expects  function  parameters  to  be  passed  as  a
sequential  list  of words starting at some memory address.  Well, our site
is running a Pyramid 90x, a RISC machine.  The architecture of the  machine
makes  it  more  efficient to pass parameters in registers, due to the it's
abundance of them (at any moment, a function has access to 64 registers: 16
global  (general), 16 local, 16 parameter, and 16 temporary).  The author's
assumption about memory parameter lists wasn't valid and subsequently,  the
program died trying to access the arguments.

I'm not defending the author  of  the  program.   He  shouldn't  have  used
_doprnt() as it is an internal function of the standard C library and there
isn't any guarantee that it will be in some future revision of  the  OpSys.
However,  if C had a _s_t_a_n_d_a_r_d method of supporting variable-length argument
lists, he probably wouldn't have resorted to using _doprnt().

Anyway, put  this  message  down  as  another  reason  to  standardize  the
language,  especially  the intentionally vague areas like variable-argument
lists.

One more thing:  I've already fixed the  program  to  printf()  instead  of
_doprnt().  Don't bother sending me instructions on how to do so.


					robert
-- 
Robert Viduya
Office of Computing Services
Georgia Institute of Technology, Atlanta GA 30332
Phone:  (404) 894-4669

...!{akgua,allegra,amd,hplabs,ihnp4,masscomp,ut-ngp}!gatech!gitpyr!robert
...!{rlgvax,sb1,uf-cgrl,unmvax,ut-sally}!gatech!gitpyr!robert

mjs@alice.UUCP (M. J. Shannon, Jr.) (11/15/84)

But there is a standard method of dealing with variable argument lists.
	#include <varargs.h>
The contents of the file differ from machine to machine, but the `entry points'
are standard.

As to why _doprnt() was used: that's pretty easy.  There is no standard
analog to printf which accepts a variable argument list *as an argument*.

	Marty Shannon
UUCP:	{alice,rabbit,research}!mjs
	(rabbit is soon to die.  Does this mean alice is pregnant?  Yup!)
Phone:	201-582-3199

hansen@pegasus.UUCP (Tony L. Hansen) (11/16/84)

< As to why _doprnt() was used: that's pretty easy.  There is no standard
< analog to printf which accepts a variable argument list *as an argument*.

Take a look at vprintf(3) in the System Vr2 manual. It is exactly what
you're asking for. Now if only it had been available and documented sooner!
(Vprintf also existed, undocumented, in System III, but not in System V.)
Now if only BSD would have picked up on it when 4.2 came along instead of
documenting _doprnt(3)!  Now if ....

					Tony Hansen
					pegasus!hansen

henry@utzoo.UUCP (Henry Spencer) (11/16/84)

> But there is a standard method of dealing with variable argument lists.
> 	#include <varargs.h>
> The contents of the file differ from machine to machine, but the `entry
> points' are standard.

Unfortunately, the ability to implement <varargs.h> is, to some extent,
machine-dependent.  There are some machines where variable argument lists
are very hard to do, and must be kludged in very machine-dependent ways.

> As to why _doprnt() was used: that's pretty easy.  There is no standard
> analog to printf which accepts a variable argument list *as an argument*.

There is, however, a portable mechanism to do this job:  sprintf followed
by passing the resulting string to whoever wants it.
-- 
				Henry Spencer @ U of Toronto Zoology
				{allegra,ihnp4,linus,decvax}!utzoo!henry

chris@umcp-cs.UUCP (Chris Torek) (11/17/84)

There are times when it is important to be able to get at the innards
of printf (i.e., _doprnt or _printf or _foobar or whatever the XYZ
software co. calls it), because the existing functions just don't Do
What You Want.  There is a wonderfully nearly portable way to do this,
and that is the v*printf routines in System V.  Sigh.

Even those may not Do What You Want.  Probably the most general way
to do things would be to have a routine called func_printf, which
takes a function $f$, an argument $caddr_t arg$, a format $fmt$, and
a variable argument list a la printf and friends.  Then the routine
that actually implements printf() et al. could call $f$, giving the
character to be printed and the extra $arg$.  If $caddr_t$ is the
generic pointer type, you can then squeeze all needed info into a
structure and pass its address.

Of course, such an implementation would be terribly inefficient for
ordinary stdio file I/O...

In the meantime, I've got #ifdef pyr's in my Emacs source.  Sigh.
-- 
(This line accidently left nonblank.)

In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (301) 454-7690
UUCP:	{seismo,allegra,brl-bmd}!umcp-cs!chris
CSNet:	chris@umcp-cs		ARPA:	chris@maryland

kpmartin@watmath.UUCP (Kevin Martin) (11/17/84)

>> But there is a standard method of dealing with variable argument lists.
>> 	#include <varargs.h>
>> The contents of the file differ from machine to machine, but the `entry
>> points' are standard.
>
>Unfortunately, the ability to implement <varargs.h> is, to some extent,
>machine-dependent.  There are some machines where variable argument lists
>are very hard to do, and must be kludged in very machine-dependent ways.
As long as they can be kludged.
That's why the definitions are hidden in an include file. That way, to
the user, they don't appear machine-dependant.

>> As to why _doprnt() was used: that's pretty easy.  There is no standard
>> analog to printf which accepts a variable argument list *as an argument*.
>
>There is, however, a portable mechanism to do this job:  sprintf followed
>by passing the resulting string to whoever wants it.
>				Henry Spencer @ U of Toronto Zoology


How about another addition to <varargs.h>, which, given a function,
its return type, and one of the (partially-used) va_list's, passes
the remaining arguments referred to by the va_list to the function?
  retval = va_apply( printf, int, nextarg );
Obviously, va_apply is a macro, but it usually needs a library routine
written in (shudder) assembler to copy and build another argument list.

Unfortunately, you still can't call fprintf unless the arglist YOU
are given also contained the file pointer. va_apply has no way of passing
args which *aren't* in the given arglist. Maybe a more general way of
building an arglist to give to va_apply is required...

(Yes, we have implemented va_apply, and I have used it)
               Kevin Martin, UofW Software Development Group

gam@amdahl.UUCP (Gordon A. Moffett) (11/18/84)

Robert Viduya (gatech!gitpyr!robert) writes:

> I'm not defending the author  of  the  program.   He  shouldn't  have  used
> _doprnt() [ for variable-length arglists ] as it is an internal
> function of the standard C library and there
> isn't any guarantee that it will be in some future revision of  the  OpSys.
> However,  if C had a standard method of supporting variable-length argument
> lists, he probably wouldn't have resorted to using _doprnt().
> 
> Anyway, put  this  message  down  as  another  reason  to  standardize  the
> language,  especially  the intentionally vague areas like variable-argument
> lists.

But is it necessary to tie this into the language?  Both System V and
BSD have varargs.h, does this not provide portable variable-arglist
functions?

(System V also provides a vprintf(3), which is used with varargs.h
 and works similarly to _doprnt()).
-- 
Gordon A. Moffett		...!{ihnp4,hplabs,amd,nsc}!amdahl!gam

37 22'50" N / 122 59'12" W	[ This is just me talking. ]

henry@utzoo.UUCP (Henry Spencer) (11/19/84)

> >Unfortunately, the ability to implement <varargs.h> is, to some extent,
> >machine-dependent.  There are some machines where variable argument lists
> >are very hard to do, and must be kludged in very machine-dependent ways.
> 
> As long as they can be kludged.
> That's why the definitions are hidden in an include file. That way, to
> the user, they don't appear machine-dependant.

You miss my point.  I didn't say that the implementation of <varargs.h>
was machine-dependent; that's obvious.  I said that the *ability* to
implement <varargs.h> is machine-dependent.  There are machines on which
that user interface to variable-length argument lists simply cannot be
implemented, period.

> How about another addition to <varargs.h>, which, given a function,
> its return type, and one of the (partially-used) va_list's, passes
> the remaining arguments referred to by the va_list to the function?

I haven't studied this closely, but I suspect that the same comment may
apply:  the ability to do this is machine-dependent.  A partially-used
argument list does not necessarily look like a shorter complete argument
list.

Hey, people, we've been over and over this point here and in unix-wizards:
THERE IS NO FULLY PORTABLE WAY TO DO VARIABLE-LENGTH ARGUMENT LISTS IN C!!!

Rebuttals by mail, please, so I can tell you why your idea won't work
without needing to thrash this out AGAIN in front of everyone.
-- 
				Henry Spencer @ U of Toronto Zoology
				{allegra,ihnp4,linus,decvax}!utzoo!henry

chris@umcp-cs.UUCP (Chris Torek) (11/20/84)

> > As to why _doprnt() was used: that's pretty easy.  There is no standard
> > analog to printf which accepts a variable argument list *as an argument*.

> There is, however, a portable mechanism to do this job:  sprintf followed
> by passing the resulting string to whoever wants it.

But this only works for (a large number of) specific cases.  Suppose a
string could be arbitrarily long?  Then you need an arbitrarily long
buffer.  A function-based printf would seem to be the ``best'' way to
be everything to everyone, but there is no such beast. Sigh.

Granted, one should use sprintf when possible.  But (as many have found
out) sprintf is dangerous, since it will merrily overflow buffers and
scribble on top of useful data.  snprintf() and sxprintf() help, but
only those who have implemented Patrick Powell's changes have those.
Again, sigh.
-- 
(This line accidently left nonblank.)

In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (301) 454-7690
UUCP:	{seismo,allegra,brl-bmd}!umcp-cs!chris
CSNet:	chris@umcp-cs		ARPA:	chris@maryland

katzung@ucsfcgl.UUCP (Brian Katzung) (11/22/84)

> But there is a standard method of dealing with variable argument lists.
> 	#include <varargs.h>
> The contents of the file differ from machine to machine, but the `entry points'
> are standard.
> 
> As to why _doprnt() was used: that's pretty easy.  There is no standard
> analog to printf which accepts a variable argument list *as an argument*.
> 
> 	Marty Shannon
> UUCP:	{alice,rabbit,research}!mjs
> 	(rabbit is soon to die.  Does this mean alice is pregnant?  Yup!)
> Phone:	201-582-3199

(I didn't see the original article so I'm not sure what was said.
My apologies if I'm repeating something.)

The "standard method" can't always be applied.  As far as I know, it
is impossible to do under some implementations (I'd be delighted to
have somebody prove me wrong by providing a non-trivial solution (ie,
one that doesn't consist of separate entry points)).

The problem arises on systems like ZILOG that put some arguments in
registers (it may speed up execution a little, but it slows me
down a lot).

Fixing argument references in the case of printf-like routines is messy,
but straight-forward:

printf(rformat, rarg1, rarg2, rarg3, sarg1)
/* r = in register, s = on stack */
char	*rformat...
{
	int	regcnt = 3;
	char	*argp = &rarg1;		/* (argp <- addr of copy of reg) */

	/*
	 * Current argument is *argp.  Next argument is:
	 */
	if (--regcnt)			/* Not last register argument. */
		(Advance pointer by appropriate amount)
	else				/* Switch from r to s args. */
		argp = &sarg1;
}

Now suppose I have a pair of functions, say "xlist" and "xvec", that have
been written on another system and have been "sprinkled" into numerous
modules.  The routines have the following structure:

xvec (vec)
xtype	*vec; 	/* ((xtype) 0) terminated vector. */
{ body }

xlist (element1)	/* Called xlist(e1, e2, ..., en, (xtype) 0); */
xtype	element1;
{
	xvec(&element1);	/* Let xvec do the work. */
}

On a Zilog system, xvec has to be split into a routine that takes a vector,
and a routine that takes the address of the first "register" argument and
the address of the first stack argument.  All occurrences of xlist-style
calls to xvec have to be changed to xvec2(&rarg1, &sarg1), and xvec
becomes "xvec2(vec, &vec[4])" (xvec2 now does the work).

				Brian Katzung  ucbvax!ucsfcgl!katzung

thomas@utah-gr.UUCP (Spencer W. Thomas) (11/24/84)

In article <394@ucsfcgl.UUCP> katzung@ucsfcgl.UUCP (Brian Katzung) writes:
>> But there is a standard method of dealing with variable argument lists.
>> 	#include <varargs.h>
>> The contents of the file differ from machine to machine, but the `entry points'
>> are standard.
>> 
>(I didn't see the original article so I'm not sure what was said.
>My apologies if I'm repeating something.)
>
>The "standard method" can't always be applied.  As far as I know, it
>is impossible to do under some implementations (I'd be delighted to
>have somebody prove me wrong by providing a non-trivial solution (ie,
>one that doesn't consist of separate entry points)).
>
>The problem arises on systems like ZILOG that put some arguments in
>registers (it may speed up execution a little, but it slows me
>down a lot).

The Pyramid is an example of a system that puts args in registers, but
they were perfectly able to write a "varargs" package.  The secret is a
little subroutine that knows where arguments are to be found and snarfs
up the first "n" of them from the registers and the rest from the stack
(or wherever), and builds a contiguous memory array of them.  Works
fine, and their implementation of sprintf (using varargs) runs fine on
their system, and on the Vax (the two places I've tried it).  I think
that with sufficient "cleverness", one can always get away with this
type of trick.

=Spencer

henry@utzoo.UUCP (Henry Spencer) (11/25/84)

> ... The secret is a little subroutine that knows where arguments are to be
> found and ... builds a contiguous memory array of them. ... I think
> that with sufficient "cleverness", one can always get away with this
> type of trick.

Does the magic routine parse the printf string to find out how many
arguments there are?  If not, how does it determine this?  Just stuffing
the register arguments "onto the front" of the in-memory arglist is a
machine-dependent trick that won't always work.  And always copying "a
safe number" of in-memory arguments doesn't work, because it assumes
that the arguments are ascending in memory, and that copying extra bytes
won't cause a memory fault or whatever.

In addition, there are some misguided "high-level-language" machines --
the PERQ is an example -- which have a related problem that this trick
doesn't solve.  Such machines *insist* on knowing how big the arglist is
for a given function, have it figuring in the calling sequence to the
point where you can't lie without disaster, and insist that the number
be a constant for each function.  And no, the PERQ doesn't (or at least
didn't, last I heard) have a JSR instruction you could use to build your
own calling sequence.  You get to choose between inefficient argument
passing for all functions (always build an arglist in memory and pass a
pointer to it, rather than passing the args directly), or forgetting
about <varargs.h> and kludging printf et al in machine-dependent ways.
-- 
				Henry Spencer @ U of Toronto Zoology
				{allegra,ihnp4,linus,decvax}!utzoo!henry

hamilton@uiucuxc.UUCP (12/12/84)

>> ... The secret is a little subroutine that knows where arguments are to be
>> found and ... builds a contiguous memory array of them. ... I think
>> that with sufficient "cleverness", one can always get away with this
>> type of trick.
>
>Does the magic routine parse the printf string to find out how many
>arguments there are?  If not, how does it determine this?

i think the "secret" subroutine he's referring to is _vaarg(), which gets
called by the va_arg macro in <varargs.h>.  however, it doesn't really
build up a list; since it's called in a sequential-access mode, it only
scans a pointer across the places where args are stashed.  it's main
job is figuring when that pointer needs to jump from the register frame
to the stack.

other lovers of _doprnt() who have migrated to a pyramid 90x, try this:

	#include <stdio.h>
	main ()
	{
	    foo ("%d %.0f %s\n", 1, 2., "3");
	}

	foo (fmt, args)
	    char *fmt;
	    int args;
	{
	    int varargs_block[3];

	    _doprnt (fmt, _vastart (varargs_block, &args), stdout);
	}

wayne ({decvax,ucbvax}!pur-ee!uiucdcs!uiucuxc!)hamilton