Path: utzoo!attcan!uunet!husc6!rutgers!mailrus!cornell!uw-beaver!uoregon!stevev From: stevev@uoregon.uoregon.edu (Steve VanDevender) Newsgroups: comp.lang.c Subject: Re: Passing Arguments In C Keywords: What Goes On Under The Covers? Message-ID: <2822@uoregon.uoregon.edu> Date: 17 Sep 88 19:30:36 GMT References: <2232@ssc-vax.UUCP> Reply-To: stevev@drizzle.UUCP (Steve VanDevender) Organization: University of Oregon, Computer Science, Eugene OR Lines: 129 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."