0

I'm reading Yung-Hsiang Lu's book, Intermediate C Programming, and I'm working through the chapter on stack memory. When defining the value address he provides this code example:

int f1(int k, int m) 
{
   return (k + m); 
} 

void f2(void) 
{ 
   int u; 
   u = f1(7, 2); 
   // RL A
}

He goes on to describe how in the example above, the address of u is stored before f1 is called in what he calls the value address because it is the address where the return function of f1 is stored.

In the frame of f2, u is stored at some address (say 100) with a garbage value. Then when f1 is called, the stack proceeds in the frame of f1 with a return location at RL A, a value address with value 100, and addresses for m and k. Execution of f1 yields the value 9, which is written to the garbage value at address 100. Then after f1's frame pops, the call stack will consist of frame f2 with symbol u at address 100 with value 9.

So far so good -- but where I'm unclear is what the value address of frame f1 would be if instead of u = f1(7, 2) we had u = f1(7, 2) + 5 or if u were some other function of f1 not equal to f1. It doesn't seem to me that the value of f1 would be simply written to the original garbage value in such a case, since u isn't equal to f1.

So I think I'm maybe not understanding what exactly he means with the "value address".

Can anyone clarify?

0

The author sounds as if he doesn't understand what he is talking about - though the problem may be in translation rather than in the author's thinking. Or perhaps he is over-simplifying for the purposes of exposition, confusing people who (like you) actually think about what they are reading.

Any description of what happens in hardware terms when C code is executed is meaningless because the designer of the compiler is allowed to do anything he likes as long as the result is the result defined by the language.

In the specific case of returning values from functions, here are some common possibilities:

  • f1 returns the value in a machine register or registers. Obviously the value has to be small enough to fit in 1 or 2 or (in some architectures) even 4 registers. Registers are very fast, which is good. It is then up to the caller to decide what to do with the value after f1 has returned. Ignoring it is one possibility (just start using the register for something else!); storing it somewhere is another; immediately passing it to a different function is another; immediately performing a requested calculation on it is yet another. A common theme is that u may not even exist at all as a memory location if memory ends up not being needed for it. As long as the compiler knows where the value you are calling u is - in this register or that register or temporarily pushed onto the stack - there is no need to use up an explicit memory location for it.

  • The calling function allocates enough memory space to contain a value of the same type as u. It passes the address of this memory to f1, and f1 stores its result at that address. This seems to be what your author is talking about. Allocating the memory may be done by subtracting a number from the stack pointer, to make space that will not be touched by normal use of the stack; or it may be done by pushing values onto the stack (with a side-effect of reducing the stack pointer in the same way); or it could even be done by allocating heap memory: there is nothing in C that requires the use of a stack.

  • f1 allocates memory for a returned value, and returns its address. It is then the caller's job to remember that this memory is allocated, and to free it when it is no longer needed. This is not common at the C-language level because of the difficulty for the compiler of making sure that the allocated memory is always freed, but there is nothing in the language that precludes it. It has the advantage that f1 may have a better idea than the caller about how much memory this particular return value requires.

As for your question about "value addresses" in more complicated expressions, the answer is that the compiler does whatever is necessary.

  • When values are returned in registers, nothing special needs to be done. The returned value can be manipulated directly (adding 5 to it, in your example), or it can be passed to another function by being pushed onto the stack (if the function takes its arguments on the stack) or by being moved to the correct register (if the function takes its argument from registers, which used to be enormously fashionable in the late 1980s but is démodé now).

  • When values are returned in previously reserved memory, the compiler quietly reserves as much memory as is required for all the intermediate return values. If you like, you can imagine this as a process of simplifying all complex expressions into single function calls whose return values are invisible variables called anonymous1, anonymous2, and so on. The compiler knows how much memory to reserve. It can also do quite a lot of re-using of this anonymous memory because it knows precisely when each invisible variable starts to be used and precisely when it stops being used.

1

..., except that a modern C compiler would turn your f2() into this:

void f2(void) 
{ 
   int u = 9; 
   // RL A
}

And if f1() was declared static, the compiler would not even emit it to the output file.

All of that is assuming that your comment, // RL A stands for some actual code that uses u. If it's really just a comment, then the compiler would turn your example into this:

void f2(void) 
{ 
}

Or, if f2() was a static, then the compiler would emit no code for it either.

0

This is not how actual C code is compiled. In reality, the variable u never has a real storage location allocated to it. Instead, the compiler stores the values 2 and 7 on the stack and calls f1. f1 performs its calculation by accessing them at locations relative to the base pointer (a register in the processor, called "ebp" on x86 machines). It then returns the result in another processor register ("eax" on x86 machines). f2 is free to do whatever it wants with it, but in the code you give it would simply discard it rather than storing it in any permanent location.

A scheme similar to the one you describe is used for returning structures, rather than single values -- perhaps that's what's being described by the book? There's a good description of how that works in the answer to this stackoverflow question.

  • Thanks for your response. He says at one point "A call stack is a simple scheme to manage the fact that, since the same function (f1) can be called from multiple places, something must track the next line of code to execute." and then after defining the frame as the return locationo and arguments of called functions he writes "Please remember that frames and symbols are for humans only. Computers do not understand frames and symbols. Instead they only work with addresses and values." – Hugo May 20 '16 at 8:33
  • Right. The frame for a function is the collection of memory that is effectively allocated to the function. It contains the return address (which on most processors is put onto the stack by the instruction that called the function) and the arguments (which are placed on the stack by the calling function), as well as any temporary data the function needs to work. On x86, these are usually referenced by copying the current stack pointer into the ebp register and using offsets to that (although some optimizing compilers may skip this step, e.g. gcc with the -fomit-frame-pointers flag). – Jules May 20 '16 at 8:38
  • But the return value of a function (at least when a function is not returning a struct) is not part of the frame -- it's stored in a processor register, and therefore doesn't have an address. – Jules May 20 '16 at 8:39
  • That makes sense. So I was sort of getting the sense that maybe he's omitting some details or trying to give a simplified but mostly correct (or correct "enough") model of how stack memory works -- kind of seems like that's what's going on? – Hugo May 20 '16 at 8:44
  • Yes, I guess so. It's certainly unusual for a book of this kind to go into the detail that it has at all. Usually they just state that there's a stack and local function variables get stored on it... :) – Jules May 20 '16 at 8:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.