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.