0

I've read that stack frames contain return addresses, function arguments, and local variables for a function. Since functions don't know where their stack frame is in memory at compile time, how do they know the memory address of their local variables? Do they offset and dereference the stack pointer for every read or write of a local? In particular, how does this work on embedded devices without efficient support for pointer accesses, where load and store addresses have to be hardcoded into the firmware and pointer accesses go through reserved registers?

Stonks
  • 3
  • 1
  • 1
  • Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. – Community Sep 23 '22 at 22:51
  • what does embedded have to do with any of this, embedded processors are not any different in this respect. – old_timer Sep 25 '22 at 03:23
  • where do you get the idea that embedded or anything has hardcoded addresses. embedded or not is the same. – old_timer Sep 25 '22 at 03:34

2 Answers2

1

The anser is, it depends on the architecture. You will have a register that contains the address of the current stack frame, EBP for x86 for instance, once you know this, individual variables are identified by their offsets into the stack frame, calculated by object size at compile time (hence the need for size to be know at compile time for local variables).

Even if a stack frame appears in different places in memory, the variables will have the same relative offset, so you can always calculate the address.

The size of the stack frame for each function is calculated at compile and included in the code so that each call can set up and clean its own frame.

ed__
  • 125
  • 5
  • 1
    `gcc -O1` and higher enables `-fomit-frame-pointer`, accessing local vars relative to RSP (the stack pointer), not wasting instructions setting up RBP as a frame pointer. The compiler always knows the distance from the stack pointer to any other point in the stack frame, since it knows when it changes the stack pointer. Except in functions that use `alloca`; then it does use the traditional E/RBP frame-pointer method because RSP moves by an amount that isn't a compile-time constant. See [this Q&A](https://stackoverflow.com/q/54346690) and [this](https://stackoverflow.com/q/31417784) – Peter Cordes Sep 27 '22 at 19:18
1

The way objects work is that the compiler or assembly programmer determines the layout of an object — the offset of each field relative to the start of the object (as well as the size of the object as a whole).  Then, objects are passed and stored as references, which are generally pointers in C and machine code.  In struct xy { int x; int y; }, we can reason that x is at offset 0 and y at offset 4 from an object reference to a struct xy.

The stack frame is like an object that contains a function's memory-based local variables (instead of struct members), and being accessed not by an object reference but by the stack or frame pointer.  (And being allocated/deallocated by stack pointer decrement/increment, instead of malloc and free.)

Both share the issue that we don't know the actual location/address of a given field (x or y) of a dynamically allocated object or stack frame position of a memory-based local variable until runtime, but when a function runs, it can compute the complete absolute address (of object fields or memory-based local variables) quite simply by adding together the base (object reference or stack/frame pointer) to relative position of the desired item, knowing its predetermined layout.

Processors offer addressing modes that help to support this kind of access, usually something like base + displacement.

Let's also note that many local variables are assigned directly to CPU registers so have no memory address at all.  Other local variables move between memory and CPU registers, and such might be considered optimization that means we don't have to access memory if the value of a variable is needed when that has recently already been loaded into a CPU register.

In many ways, processors for embedded devices are like other processors, offering addressing modes to help with memory accesses, and with optimizing compilers that can make good decisions about where a variable lives.  As you can tell from the above, not all variables need live in memory, and some live in both in memory and in CPU registers to help reduce memory access costs.

Erik Eidt
  • 23,049
  • 2
  • 29
  • 53