As a linked list of stack frames, the structure could be thought of as:
struct _STACK_FRAME { void * prev ; // a double word for i386 char frame[] ; // indeterminate length } ;We could be a bit more specific and write into the specification that the base of the frame contains the return address,
struct _CLOSED_STACK_FRAME { void * prev ; void * return_address ; char frame [] ; }An open stack frame, that is the current, has esp pointing to its current base, and ebp is the root of the list of closed stack frames.
This does not capture it all, however, because ebp has a currently living value as the base of the current stack, or perhaps better, the current heap of local variables. I find this dual role confusing unless it is cleanly described.
Hence a more accurate description is a linked list of stack-heap pairs, with the thread running physically through the center:
struct _STACK_HEAP_PAIR { char current_heap ; void * prev_stack_heap_pair ; char prev_frame [] ; } ;where we note that the first item of previous_is the return address and implicitly the value of the previous stack pointer.
As a picture:
+-------+ +-------+ | esp | | ebp + +------- +-------+ | | +---+ | | | v | +------------- | | stack frame D .. | +------------- | | v +---------------+------+-------------+ | stack frame C | prev | heap D | +---------------+------+-------------+ | v +---------------+------+-------------+ | stack frame B | prev | heap C | +---------------+------+-------------+ | v +---------------+------+-------------+ | stack frame A | prev | heap B | +---------------+------+-------------+The labels applied to the heaps and stack frames associate them logically, whereas the drawing suggests their physical association. For instance, stack from D and heap D are currently active. The most recently closed frame and head are those labeled C.
It is also true that these pairs are arranged descending and contiguous in memory. For instance, the left edge of stack frame B goes right up against the right edge of heap B. This fact is surprisingly useless. Neither the depth of the frame nor the height of the heap are ever recorded - they are "compiled in" as either the size of the parameter list of the call or the size of the local variable block.
In other words, the boundary between a stack and its heap is not visible in register settings or standard global variables, nor need it be.
Burton Rosenberg
11 September 1998