Passing Arguments In C

Steve VanDevender stevev at uoregon.uoregon.edu
Sun Sep 18 05:30:36 AEST 1988


In article <2232 at ssc-vax.UUCP> dmg at 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 at drizzle.cs.uoregon.edu	stevev at 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."



More information about the Comp.lang.c mailing list