[comp.lang.c] passing variable arguments

chin@ankh.ftl.fl.us (Albert Chin) (06/07/90)

How does printf() work. I believe there is some way in C to obtain the
pointer to the argument list in a function. If so, printf() is pretty 
simple. But how do you do this. Is it possible to get the number of
arguments in a printf() statement?

albert chin ... mthvax!mamia!albert

dankg@tornado.Berkeley.EDU (Dan KoGai) (06/08/90)

In article <353@ankh.ftl.fl.us> chin@ankh.ftl.fl.us (Albert Chin) writes:
>How does printf() work. I believe there is some way in C to obtain the
>pointer to the argument list in a function. If so, printf() is pretty 
>simple. But how do you do this. Is it possible to get the number of
>arguments in a printf() statement?

	On C convention, arguments are pushed to the stack right to left
order before it jumps to the function.  Since you can push as many arguments
as your memory spec allows, it's quite possible, as you see on printf, to
make variable argument functions on C.
	However, variable argument functions are somewhat kludgy on C:  Bad
news is that you have to explicity include the number of arguments somewhere
in your argument and there's no other way of getting number of arguments:
in printf() and scanf(), et al., It's number of '%'s in format and it might
crash if you give wrong number of arguments.  Also note there's no way
you can tell the type of arguments in variable arg functions--printf() uses
format to specify it and even though you feed wrong type args in printf()
it uses argument type implied in format--it's a common mistake to pass
int, instead of pointer to int in scanf().
	On Ansi C, you can use <stdarg.h> macro package to write variable
argument function.  And variable argument is implied by "...".  Here's simple
example:

#include <stdarg.h>

/* multiple strcat */

char *mstrcat(int, nsrc, char *dst, char *src1, ...)	
/*... means more args to come */
{
	va_list srcs;	/* va_list is typically void ** */
	va_start(srcs, src1);	/* now srcs = &src1 */
	while(nsrc--){	/* repeat nsrc times */
		strcat(dst, va_arg(char *, srcs));
		/* va_arg(type, valist) returns *(type)valist 
		  then inclements valist */
	}
	va_end(srcs)	/* clears stack or do nothing: compiler dependent */
	return dst;
}

	You can get more details from K&R.  The most important thing to
remember is that in C you have to know how many number of what types of
arguments are there.  You C compiler will not take care of you.

----------------
____  __  __    + Dan The "printf("%d", printf("d", printf.." Man
    ||__||__|   + E-mail:	dankg@ocf.berkeley.edu
____| ______ 	+ Voice:	+1 415-549-6111
|     |__|__|	+ USnail:	1730 Laloma Berkeley, CA 94709 U.S.A
|___  |__|__|	+	
    |____|____	+ "What's the biggest U.S. export to Japan?" 	
  \_|    |      + "Bullshit.  It makes the best fertilizer for their rice"

emm@iczer-1.UUCP (Edward M. Markowski) (06/09/90)

In article <353@ankh.ftl.fl.us> chin@ankh.ftl.fl.us (Albert Chin) writes:
>How does printf() work. I believe there is some way in C to obtain the
>pointer to the argument list in a function. If so, printf() is pretty 
>simple. But how do you do this. Is it possible to get the number of
>arguments in a printf() statement?

The way I was learned to pass a varible number of arguments was to make
the first argument some value that could be counted.

Printf gets the address of the first argument( the format string ) it
scans it for % chars. When it finds a % char it uses the address of the
format string to find nhe next arg. The address of the format string
is a pointer to a location in the parameter stack. Printf just adds
( or subtracts ) the currect size from the pointer to the stack to get
the next parameter. Printf stops going up the stack when it runs out
of % chars in the format string, even if you did not pass it the correct
number of args.

The compiler does add any code to let the called function know how many
parameters there are for it on the stack. You must make shure your
function expects the same number of parameters as it was passed or
pass a variadle letting it know how many parameters there are.



-- 
Edward M. Markowski -- iczer-1 Administrator

                              ...the garage is flooded from the sprinkler.
                              It also left a man's decapitated body,
VOICE : (201) 478-6052        lying on the floor next to his own severed head.
UUCP : ..!uunet!iczer-1!emm   A head which at this time has no name.

mcdaniel@amara.uucp (Tim McDaniel) (06/10/90)

In an otherwise excellent article, dankg@tornado.Berkeley.EDU (Dan
KoGai) make a few minor errors:

> On C convention, arguments are pushed to the stack [in] right to
> left order before it jumps to the function.

Not so.  Arguments are often pushed on a stack left to right, or
however the implementation likes.  (Consider an architecture with an
"argument pointer" register, pointing to the leftmost argument.)  Some
implementations put a few arguments in registers for efficiency: they
could be the rightmost few or the leftmost few.  Some calling
conventions pass an argument count, but ANSI C doesn't require it.  A
smart compiler can see when there's no recursion and thus no need for
a stack, and is permitted to put arguments in a static area or
"inline" the function.  In ANSI C, if a function has a variable number
of arguments (like printf), it must be called with a function
prototype in scope ending in ",...)", to tell the compiler this fact.
The compiler may (and sometimes must) choose a different
parameter-passing convention for ",..." functions than for a normal
prototyped or unprototyped function.  Certain schemes may be easier to
implement than others, but it is a serious mistake to depend on a
particular argument-passing scheme, and only the implementor has to
know how it actually works.

For ",..."  functions, the passing method has to be such that the callee
- can find the first variable argument given the last known argument
  (for va_start)
- can get the next variable argument given the current one and the
  next one's expected type (for va_arg)
The callee need not be told, and cannot (portably) find out
- the number of actual arguments
- the type (or even sizeof) of each actual argument
With such loose requirements, I can think of any number of schemes.
As dankg wisely notes, it is free to crash if you make a mistake in
number or types.

To stomp on a common misconception, by the way: section 3.3.2.2 of the
ANSI C standard, page 42, lines 20-21: "The order of evaluation of the
function designator, the arguments, and subexpressions within the
arguments is unspecified, but there is a sequence point before the
actual call."  Translation: however programs actually pass arguments,
you can't depend on the order of evaluation of side-effects in
expressions in a function call, except that the side-effects take
place before the actual call.

> you have to explicity include the number of arguments somewhere in
> your argument[s] and there's no other way of getting number of
> arguments

The two usual methods are
- an argument count (e.g. in printf, derived from the contents of the
  format string).
- a sentinel: the last argument is (int)0 or (char *)0 or some other
  special value.

> in printf() and scanf(), et al., It's number of '%'s in format

Almost: "%%" means "output a single %", and there's no argument in
this case.  Also, "%*" starts a scanf conversion with suppressed
assignment, and there must not be an argument for it.  (I know, "pick
pick pick".)

> And variable argument is implied by "...".

Actually, by ",...".  You must have a least one non-variable argument:
   extern foo(...);
is not valid in ANSI C.

   > char *mstrcat(int, nsrc, char *dst, char *src1, ...)
Remove first ",".
   > /*... means more args to come */
   > {
   > 	va_list srcs;	/* va_list is typically void ** */
Don't assume anything, typical or not.  A va_list is a va_list, period.
   > 	va_start(srcs, src1);	/* now srcs = &src1 */
   > 	while(nsrc--){	/* repeat nsrc times */
   > 		strcat(dst, va_arg(char *, srcs));
Arguments reversed to va_arg: va_list followed by type.
   > 		/* va_arg(type, valist) returns *(type)valist 
   > 		  then inclements valist */
   > 	}
   > 	va_end(srcs)	/* clears stack or do nothing: compiler dependent */
Must have a ";" at the end.
   > 	return dst;
   > }

General problem: va_arg starts with the first variable argument.  The
example as given skips the last known argument, "src1".  It has to be
special-cased outside the loop.

For this function, I would have used a sentinel rather than a count.
The last argument would have to be "(char *)0" in such a scheme.

> You can get more details from K&R.  The most important thing to
> remember is that in C you have to know how many number of what types
> of arguments are there.  You C compiler will not take care of you.

Exactly!
--
"I'm not a nerd -- I'm 'socially challenged'."

Tim McDaniel
Internet: mcdaniel@adi.com             UUCP: {uunet,sharkey}!puffer!mcdaniel

platt@ndla.UUCP (Daniel E. Platt) (06/11/90)

In article <353@ankh.ftl.fl.us>, chin@ankh.ftl.fl.us (Albert Chin) writes:
> How does printf() work. I believe there is some way in C to obtain the
> pointer to the argument list in a function. If so, printf() is pretty 
> simple. But how do you do this. Is it possible to get the number of
> arguments in a printf() statement?



If you have a function 'foo', defined:

void	foo(a, b, c)
char	a, b, c;
{
	char	*pt;

	/* get a pointer to the stack */
	pt = &a;

	/* 
	**	Then:
	**		pt[0] <-> a;
	**		pt[1] <-> b;
	**		pt[2] <-> c;
	*/

	...
}

C is set up to put the first argument on the top of the 'stack' (if
there is one).  Then in the case of 'printf', you can get the number
of arguments by counting the number of  '%'s (actually, you have to be a
bit careful since '%%' is the symbol for the character '%').  Types, and
therefore implementation defined sizes, are determined from what follows
the % escape.

It turns out that you have to be careful about sizes of things, and this
isn't portable.  There are macro standards set up to handle this called
'varargs.'  


Hope this helps,

Dan

fmcwilli@oracle.oracle.com (Floyd McWilliams) (06/12/90)

In article <1990Jun8.165259.8368@agate.berkeley.edu>
  dankg@tornado.Berkeley.EDU (Dan KoGai) writes:
>	On C convention, arguments are pushed to the stack right to left
>order before it jumps to the function...

	The order in which arguments are placed on the stack is implementation-
defined.  (In fact, there's nothing that requires compilers to place arguments
on the stack -- they can be passed through registers.)

--
	Floyd McWilliams -- fmcwilli@oracle.com
	Protest censorship -- buy an "obscene" album next time you're in
the Florida Soviet Federated Socialist Republic.

schlut@oski (Olaf Schlueter) (06/12/90)

In article <1990Jun8.165259.8368@agate.berkeley.edu> dankg@tornado.Berkeley.EDU (Dan KoGai) writes:
> In article <353@ankh.ftl.fl.us> chin@ankh.ftl.fl.us (Albert Chin) writes:
> >How does printf() work. I believe there is some way in C to obtain the
> 	On C convention, arguments are pushed to the stack right to left
> order before it jumps to the function.  
> 	On Ansi C, you can use <stdarg.h> macro package to write variable
> argument function.  And variable argument is implied by "...".  Here's simple
> example:
[good example, which everyone shall use from now on and forever, deleted]

What Dan means with 'C convention' has never been an assured standard and
everyone should avoid this. It is possible to use another parameter passing
scheme even in K&R (prae ANSI) C. Before ANSI C there has been no portable
way to write a routine like printf in C. We should be glad that this has 
changed and forget about the elder days. Even with parameter passing on
stack assured, how will you port a programm written on a compiler with
32 Bit int's to one with a different int size ?

Mfg, (avoid E-Mail whenever possible, I have to pay for it)


--
Olaf Schlueter, Sandkuhle 4-6,	! subnet:		schlut@oski
2300 Kiel 1, West Germany	! USENET/DNET/EUNET: 	schlut@tpki.de 
(soon: Germany ?!)		! ZERBERUS: 		schlut@KBBS
--- Und bitterer noch als der Tod ist das Weib (Pred 7,26) --------

scs@adam.mit.edu (Steve Summit) (06/12/90)

This topic is discussed in the frequently-asked questions list
(question 25 in the June posting).  Additionally, Tim McDaniel
posted a number of corrections and explanations, in article
<MCDANIEL.90Jun9154616@angler.amara.uucp>, to some previously-
posted code.

In article <314@ndla.UUCP> platt@ndla.UUCP (Daniel E. Platt) writes:
>...
>	/* get a pointer to the stack */
>	...
>C is set up to put the first argument on the top of the 'stack' (if
>there is one).

...but there might not be one, and even if there is one there is
no guarantee how it might be set up, which is why...

>...this isn't portable.  There are macro standards set up to handle this
>called 'varargs.'

The new, standard macro package (ANSI C) is called <stdarg.h>.
Don't try to pick arguments apart "by hand;" use the standard
macro package.  If your compiler doesn't supply it yet, you may
be able to make use of someone else's implementation of stdarg.h .
This is not guaranteed to work, as the innards of a standard
header may be tied to a particular implementation (particularly
for something as machine-dependent as variable-length argument
lists), but it is possible to write a semiportable stdarg.h that
will work anywhere a "standard" macro-only (i.e. no special-cased
compiler support) varargs.h would work.  I posted one to
alt.sources some time ago.  An amended version is attached.

(If stdarg.h is not an option, you could also try using the old
varargs package, with which stdarg is partly compatible).

                                            Steve Summit
                                            scs@adam.mit.edu

----8<------8<----cut here for simple stdarg.h----8<------8<-----
/*
 *  stdarg implementation for "conventional" architectures with
 *  downward-growing stacks.  The stack is assumed to be aligned
 *  to word, i.e. int, sized boundaries, with smaller arguments
 *  widened when passed and larger arguments equal to (or rounded
 *  up to) a multiple of the word size.
 *  Note that this code (and indeed, any simple macro-based
 *  approach) cannot possibly work on architectures which pass
 *  arguments in registers.
 *
 *  Another problem is that it doesn't (and can't, without help
 *  from the compiler) know that float arguments are widened to double
 *  when passed.  Don't, therefore, call va_arg(..., float).
 *
 *  Steve Summit  4/15/89
 *
 *  This code is Copyright 1989 by Steve Summit.
 *  It may, however, be redistributed and used without restriction.
 */

typedef int __stacktype;
typedef char __argptype;
typedef __argptype *va_list;

#define __argsize(thing) ((sizeof(thing) + (sizeof(__stacktype) - 1)) \
	/ sizeof(__stacktype) * (sizeof(__stacktype) / sizeof(__argptype)))

#define va_start(vaptr, lastarg) \
			vaptr = (va_list)&lastarg + __argsize(lastarg)

#define va_arg(vaptr, type) \
		(*(type *)(((vaptr) += __argsize(type)) - __argsize(type)))

#define va_end(vaptr)

flaps@dgp.toronto.edu (Alan J Rosenthal) (06/17/90)

platt@ndla.UUCP (Daniel E. Platt) writes:
>void	foo(a, b, c)
>char	a, b, c;
>{
>	char	*pt;
>	pt = &a;
>	/* 
>	**	Then:
>	**		pt[0] <-> a;
>	**		pt[1] <-> b;
>	**		pt[2] <-> c;
>	*/

EXTREMELY unlikely.  `char' in "old-style" declarations declares an argument
passed as an integer and converted to a char.  If you're unlucky it won't even
be on the argument stack.

What you say would work for rvalue types such as int, double, long, any pointer
type.

ajr