[comp.lang.c] Passing Arguments In C

dmg@ssc-vax.UUCP (David Geary) (09/17/88)

  I'm trying to get a grasp on what actually goes on under the covers
when passing arguments in C.  Here's how I understand the process, please
feel free to correct me if I'm wrong:

Let's say we have the following:

DoIt(x,y,z)
  int x,y,z;
{
  /* Does something... */
}

main()
{
   int a=2, b=3, c=4;

   DoIt(a,b,c);
}

Simple enough? ;-)  Anyway, here goes...

When main() calls DoIt(), the values of a,b, and c are loaded on
a stack somewhere in memory.  However, as I understand it, the
top of the stack is at a higher address than the bottom.  Here's what
I mean:

1)  Load a on the stack (args are loaded left to right, correct?)

  	ADDRESS		VALUE

	106000		2

2)  Then b is pushed on the stack:

	ADDRESS		VALUE

	105996		2
	106000		3

3)  Then c is pushed on the stack:

	ADDRESS		VALUE

	105992		2
	105996		3
	106000		4

Notice that the stack's "top" is at location 106000.  In other words
the stack grows toward the top of memory.  Also, of course, the ADDRESS
locations were made up off the top of my head. I am also assuming, for
the sake of example, that int's take up 4 bytes in memory. 

Then, I guess, the address of the calling routine is loaded onto the
stack, so that we know where to return when DoIt() is done.  So, 
the stack looks like so when everything is pushed on:

	ADDRESS		VALUE

	105988		Address of main() (assume 4 bytes for ptrs, too)
	105992		2
	105996		3
	106000		4

So, this should be the condition the stack is in when DoIt() takes over.


When DoIt() takes over, it only knows where the stack starts in memory,
and then uses that starting address and offsets to access the x,y, and z
in DoIt().



Am I thinking correctly?  I've never written a compiler, so I am not real
sure about this.  Also, I'm a bit confused about what is left up to the
compiler as far as HOW to implement the passing of variable values.  Does
the compiler HAVE TO do it this way.  Does the compiler have the freedom
to, say, grow the stack towards the end of memory instead of the beginning?
Where is all this kind of stuff spelled out?

I teach an Advanced C class, and am showing my students how to write variadic
functions.  I want to make sure that what I'm telling them is correct, and 
also want to prepared to answer the questions in the above paragraph.  

Any help, questions, comments, etc. are greatly appreciated.

Thanx

-- 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ David Geary, Boeing Aerospace,               ~ 
~ #define    Seattle     RAIN                  ~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

stevev@uoregon.uoregon.edu (Steve VanDevender) (09/18/88)

In article <2232@ssc-vax.UUCP> dmg@ssc-vax.UUCP (David Geary) writes:
>Let's say we have the following:

>DoIt(x,y,z)
>  int x,y,z;
>{
>  /* Does something... */
>}

>main()
>{
>   int a=2, b=3, c=4;
>
>   DoIt(a,b,c);
>}

>When main() calls DoIt(), the values of a,b, and c are loaded on
>a stack somewhere in memory.  However, as I understand it, the
>top of the stack is at a higher address than the bottom.  Here's what
>I mean:

>1)  Load a on the stack (args are loaded left to right, correct?)

Incorrect.  The compiler may choose to load arguments in any order it
wants.  Many compilers load arguments right to left, in order to place
the first argument at the lowest address.

>2)  Then b is pushed on the stack:
>3)  Then c is pushed on the stack:

>	ADDRESS		VALUE

>	105992		2
>	105996		3
>	106000		4

>Notice that the stack's "top" is at location 106000.  In other words
>the stack grows toward the top of memory.  Also, of course, the ADDRESS
>locations were made up off the top of my head. I am also assuming, for
>the sake of example, that int's take up 4 bytes in memory. 

Most stacks grow _downwards_, and the term "top-of-stack" usually
refers, then, to the _lowest_ address in the stack.  "Top-of-stack"
refers to the last-pushed stack element, but need not imply that the
top has the highest address.  Many compilers choose to push arguments
in such a way that the left-most argument has the lowest stack
address, which is why, if the stack grows downwards, the arguments are
pushed right-to-left.  Upon function entry, the value of the stack
pointer is saved and used as a base for indexing into the stack.
The indexing may not start at 0, either, since the return address and
some registers may be pushed after the function arguments.

>Then, I guess, the address of the calling routine is loaded onto the
>stack, so that we know where to return when DoIt() is done.  So, 
>the stack looks like so when everything is pushed on:
>
>	ADDRESS		VALUE
>
>	105988		Address of main() (assume 4 bytes for ptrs, too)
>	105992		2
>	105996		3
>	106000		4
>
>So, this should be the condition the stack is in when DoIt() takes over.

Why is the address of main() not loaded onto the top-of-stack?  This is also
an unusual kind of stack in that the entire contents of the stack must be
moved up and down in memory to push and pop values.  Usually there is a
top-of-stack pointer that is incremented and decremented to push and pop
values, so the top-of-stack isn't always at the same address.

>Am I thinking correctly?  I've never written a compiler, so I am not real
>sure about this.  Also, I'm a bit confused about what is left up to the
>compiler as far as HOW to implement the passing of variable values.  Does
>the compiler HAVE TO do it this way.  Does the compiler have the freedom
>to, say, grow the stack towards the end of memory instead of the beginning?
>Where is all this kind of stuff spelled out?

>I teach an Advanced C class, and am showing my students how to write variadic
>functions.  I want to make sure that what I'm telling them is correct, and 
>also want to prepared to answer the questions in the above paragraph.  

>Any help, questions, comments, etc. are greatly appreciated.

>Thanx

First of all, although I've made several comments on how C argument
passing and stacks usually work, in my experience, you don't need to
know how C argument passing works, and shouldn't make any assumptions
about its inner workings.  You can usually depend on being able to
write functions with variable numbers of arguments, but this depends
on there being some way for you to access the variable part of the
argument list.

Remember that a function taking a variable number of arguments _must_
either have fixed arguments that determine the number of variable
arguments, or use a special argument value as a terminator.  Note that
functions like printf() and scanf() have fixed arguments leftmost, or
functions like some of the ones in the UNIX exec() system call family
use a NULL to mark the end of arguments.  For example, the declaration
of printf() usually looks something like this:

int printf(format, args)
char *format;
int args;
{
	char *argp;
	...

	argp = (char *) &args;
	...
}

The pointer argp is then advanced through the arguments based on the 
format specifiers found in the format string.  Writing printf() in this
way would make it quite non-portable, since one may not be able to
depend on successive arguments occuring at higher addresses.  Look at
the header file varargs.h on a UNIX system, or better yet, on several
different UNIX systems to see the differences, and see how functions with
variable numbers of arguments can be portably defined.

Disclaimer:  I haven't written a compiler, either, and sadly, I'm most
familiar with C on my MS-DOS system, although I also use C under UNIX
regularly.
-- 
Steve VanDevender 	stevev@drizzle.cs.uoregon.edu	stevev@oregon.BITNET
"Bipedalism--an unrecognized disease affecting over 99% of the population.
Symptoms include lack of traffic sense, slow rate of travel, and the
classic, easily recognized behavior known as walking."

charette@edsews.EDS.COM (Mark A. Charette) (09/19/88)

One small point - assuming a conventional stack arrangement for passing
values to a function is not ALWAYS correct. I did a (very) little bit
of programming in the 'B' language supplied on a Honeywell 6060 Level 66
a few years back. (I couldn't do too much with it - the on-line manual 
wasn't complete. I think the language was supplied by the University of
Waterloo. It was probably an accident that we had it at all.) Anyway,
the "stack" was an area of memory set aside for parameter passing, set
up as a circular queue. The post-mortem debugger supplied with the language
could read the queue and trace the program back with all of the values
passed to the functions intact (until the queue wrapped).
It was valuable (to me) since I could look at the history of the program
and values.

-- 
Mark Charette                        "Unmitigated seriousness is always out of
Electronic Data Systems               place in human affairs." - G. Santayana
750 Tower Drive              Voice: (313)265-7006
Troy, MI 48007-7019          charette@edsews.eds.com     uunet!edsews!charette 

blarson@skat.usc.edu (Bob Larson) (09/19/88)

In article <2232@ssc-vax.UUCP> dmg@ssc-vax.UUCP (David Geary) writes:
>  I'm trying to get a grasp on what actually goes on under the covers
>when passing arguments in C.
[...]
>When main() calls DoIt(), the values of a,b, and c are loaded on
>a stack somewhere in memory.

When main() calls DoIt(), the arguements are calculated in random
order then passed via whatever trick the compiler writer thought
approprate.  (Stack?  Why do you assume there is one?)

>Am I thinking correctly?

Your sceme should work, but it certainly isn't the only or even the 
most common way.

>Does the compiler have the freedom
>to, say, grow the stack towards the end of memory instead of the beginning?

The compiler can use anything that works.  Os9/68k passes the first two
arguments in registers, Prime 64v mode C creates temporaries then passes
the address of them, etc.

>Where is all this kind of stuff spelled out?

It isn't spelled out on purpouse. The compiler writer can choose what
is best under the circumstances.  (K&R, K&R II, ansi drafts, and H&S
are possible referneces.)

>I teach an Advanced C class, and am showing my students how to write variadic
>functions.

There are two semi-portable ways of doing it, known as varargs and stdargs.
(varargs comes from bsd, stdargs from ansi.)  If using varargs, remember
to get ALL arguments to the function that way.  (Some varargs implemetations
depend on this.)  stdargs implementations are still rare, and will frequently
require compiler support.

For C implementations that don't come with varargs.h, I have written my
own.  To do this, you read the compiler specific documentation, and
write some example programs and study the assembler output.

If you must show an example of argument passing, you should show as
wide a variety of them as possible so your students don't get stuck
in the all the worlds a vax philosiphy.
Bob Larson	Arpa: Blarson@Ecla.Usc.Edu	blarson@skat.usc.edu
Uucp: {sdcrdcf,cit-vax}!oberon!skat!blarson
Prime mailing list:	info-prime-request%ais1@ecla.usc.edu
			oberon!ais1!info-prime-request

cquenel@polyslo.CalPoly.EDU (Rodent Of Unusual Size) (09/19/88)

In article <2232@ssc-vax.UUCP> dmg@ssc-vax.UUCP (David Geary) writes:
>
>I teach an Advanced C class, and am showing my students how to write variadic
>functions.  I want to make sure that what I'm telling them is correct, and 
>also want to prepared to answer the questions in the above paragraph.  
>Any help, questions, comments, etc. are greatly appreciated.
>Thanx

I saw another reply that dealt with stacks and some basics, but
I thought I could add a little more.  I'm not a full-fledged, compiler
weenie, but I did work on one for a while.

I recently had to come up with an implementation of varargs for a 
compiler for a new architecture, and it was a pain.

The compiler is free to decide where/when/if to stick any of the following
on the stack : parameters, stack pointer, frame pointer, return value.
In general either the stack or frame pointer must be saved on the
stack along with the return value (which NOT the address of the routine, 
by the way, but the address of the instruction following the call, this
is where execution of the interrupted routine will continue) must be saved
on the stack. Debuggers work more easily and better if they can look back
on the stack and see ALL the parameters and both the stack and frame
pointers, and the return address, but many compilers do away with some
of this to make the code execute faster.

Perhaps the most common variation to the standard "dump everything
on the stack" approach is to pass the first X parameters in registers.

This is what I primarily wanted to tell you about.

All a C programmer REALLY needs to know about is that the VARARGS macros
should work as documented.  Compiler writers are the ones that have to deal
with the headaches.  The problems stem from the fact that the compiler 
cannot depend on having a "prototype", (that is, a definition of the
types of parameters passed to a routine) when it generates code for the 
procedure call.  This necessitates the following scheme:
	Call all procedures as if they WERE NOT varargs routines (that
is, pass X parameters in registers) and let the varargs routine deal
with it as it sees fit, BUT reserve stack space on the stack for the
parameters you didn't save (the reason for this will be made clear).

When the compiler compiles a varargs routine, the first code generated dumps
the first parameters from the incoming registers onto the stack, into
the space reserved for them.  This gives a nice consecutive string
of parameters in memory for varargs to step through.

This is necessary because the common usage of VARARGS allows an
abstract parameter list to be /passed to other functions/.  The only
practical method for doing this is with a pointer, and for that the
paramaters need to be on the stack.

NOTE: This scheme allows all the varargs macros to be implemented without 
function calls, as simple pointer expressions that increment/dereference
a pointer.  It is also possible for a compiler to implement varargs
as a procedure call to a routine that "figures out" a more complicated
scheme.

Also note.  This scheme requires that the X registers used for
parameters BE TREATED AS IF THEY WERE WORDS OF MEMORY ON THE STACK.
Even if the first 4 bytes of a structure have to shoved in a register,
and the rest put on the stack.

Basically, passing structures by value (NOT passing a pointer)
f*cks you over real good.

In theory it is possible to do whatever you want with the varargs
macros because you tell them the type of the thing you want, but in 
practice the only thing you can do with a type is take it's size,
and an 8 byte double precision value the compiler would like to
stick in a register, and an 8 byte struct the compiler would like
to shove on the stack where it shoves all the other structs.

Anyway, I digress, I hope that gives more than enough information
than you wanted to know about variadic functions in C

To quote someone who IS a compiler weenie (whose name I forget
for the moment [Mark, can you tell me ?]) :

"When varargs strikes there are no survivors."

:-)

-------------------------------------------------------------------------------
| Chris Quenelle            | Smart Mailers -> cquenel@polyslo.CalPoly.EDU    |
| Computer Systems Lab      | Dumb Mailers  -> !ucbvax!voder!polyslo!cquenel  |
| Cal Poly State Univ.      |-------------------------------------------------|
| San Luis Obispo, CA 93407 | /earth is 98% full, please delete anyone you can|
-------------------------------------------------------------------------------

gwyn@smoke.ARPA (Doug Gwyn ) (09/19/88)

In article <12241@oberon.USC.EDU> blarson@skat.usc.edu (Bob Larson) writes:
>There are two semi-portable ways of doing it, known as varargs and stdargs.
>(varargs comes from bsd, stdargs from ansi.)

Come on, <varargs.h> was written at Bell Labs and has been provided on
all releases of UNIX in recent history.  <stdarg.h> resembles it to
some degree but relies on a couple of extra hooks to ensure that it can
be reasonably implemented on all architectures.  In particular it needs
the (, ...) notification to the compiler of the special nature of a
variadic-argument function.

gwyn@smoke.ARPA (Doug Gwyn ) (09/19/88)

In article <4032@polyslo.CalPoly.EDU> cquenel@polyslo.UUCP (Rodent Of Unusual Size) writes:
>I'm not a full-fledged, compiler weenie, but I did work on one for a while.

Hey, that's pretty good!  Especially given the "Keywords" in the header.

scs@athena.mit.edu (Steve Summit) (09/20/88)

In article <2232@ssc-vax.UUCP> dmg@ssc-vax.UUCP (David Geary) writes:
>I teach an Advanced C class, and am showing my students how to write variadic
>functions.  I want to make sure that what I'm telling them is correct, and 
>also want to prepared to answer the questions in the above paragraph.  

As several people have pointed out, with incongruous reservations,
the right ways to write a variadic function are with varargs and
stdarg.  I'd like to:

  1. remove the reservations -- these are the only right ways to do it

  2. encourage you not to get your students too involved in what's
     going on "under the covers," even after your largely correct
     suppositions have been corrected by several previous articles.

There is a widespread conception that C can only be understood in
the context of the machine code being generated underneath.
Understanding that code is indeed fascinating, but exploring it
and writing programs which make use of it usually leads to
unwarranted further confusion.

Too often impressionable new students of the C language "learn"
the unintended lesson that all C programs are arcane masterpieces
of obfuscation which depend for their operation and understanding
on specific details of the underlying hardware.  There are "Intro
to C programming" books, intended for beginners, which are filled
with program upon machine-dependent program for finding out how
the particular implementation works.  If you're interested in
writing clean, portable code, you don't need or want to know this
stuff.  But if all of the example programs you see while learning
are obscure, those are the kinds of programs you will write.

                                            Steve Summit
                                            scs@adam.pika.mit.edu

P.S. I'm sure that the heresy in this article will get flamed by
     all of the obfuscation fans out there, and I'd list and
     rebut all of the counterarguments I'm sure I'll get, but
     it's late and I want to keep this article short, so I guess
     I'll have to deal with them as they come.