0

As far as I understand, stack variables are stored using an absolute offset to the stack frame pointer. But how are those variables addressed later? Consider the following code:

#include <iostream>
int main()
{
    int a = 0;
    int b = 1;
    int c = 2;
    std::cout << b << std::endl;
}

How does the compiler know where to find b? Does it store its offset to the stack frame pointer? And if so, where is this information stored? And does that mean that int needs more than 4 bytes to be stored?

Anna
  • 97
  • 5
  • It will generally be in the addressing modes/fields of the generated machine code. and the compiler (mostly) is the one that decides where to put it. So the compiler decides & uses that info to generate the code. – Avi Berger Oct 04 '22 at 21:31
  • 3
    The generated assembly may help: https://godbolt.org/z/6zhsj4Mnd You can compare between unoptimized and optimized versions. In the optimized version `b` is never stored on the stack. – Retired Ninja Oct 04 '22 at 21:39
  • You can see where the ints are stored by printing the address of the variables. See [this](https://godbolt.org/z/5q494cPe4) – doug Oct 04 '22 at 22:18
  • *How does the compiler know where to find b?* -- With that piece of code, the compiler could have not generated anything for a, b, or c, and just had `std::cout << 1 << std::endl;`. The program that you write is only a description of what you want to occur. You described outputting `1` in a roundabout way -- the compiler detects this, and skips all of the excess. This is known as the [as-if rule](https://en.cppreference.com/w/cpp/language/as_if). – PaulMcKenzie Oct 04 '22 at 22:25
  • Why do people ignore the use of registers? The compiler is allowed to bypass the stack and store variables in registers. Variables only need to be stored in memory if there is a pointer to them, or their addresses requested. How is your question affected when the compiler pushes a register on the stack (for temporary protection) and restores it (pops) later? – Thomas Matthews Oct 04 '22 at 23:15
  • @doug: if the address of a variable is taken, it can't be stored in a register. The variables could live their lives in registers without using the stack. The way to verify this is to view the assembly language listing of the function. Printing out the address changes the assembly language. – Thomas Matthews Oct 04 '22 at 23:17
  • @ThomasMatthews Agree. Objects can be stored in registers or nowhere at all under the "as if" rule. But there are only so many registers and it's good for programmers to understand how objects are stored on the stack if insufficient registers or they are passed as reference parameters. It's also useful for programmers to know about the relatively small storage limits of the stack v heap. Checking out generated code for both release and debug from time to time should be part of every programmer's activities. – doug Oct 05 '22 at 02:50

1 Answers1

2

The location (relative to the stack pointer) of stack variables is a compile-time constant. The compiler always knows how many things it's pushed to the stack since the beginning of the function and therefore the relative position of any one of them within the stack frame. (Unless you use alloca or VLAs1.)

On x86 this is usually achieved by addressing relative to the ebp or esp registers, which are typically used to represent the "beginning" and "end" of the stack frame. The offsets themselves don't need to be stored anywhere as they are built into the instruction as part of the addressing scheme.

Note that local variables are not always stored on the stack.
The compiler is free to put them wherever it wants, so long as it behaves as if it were allocated on the stack.
In particular, small objects like integers may simply stay in a register for the full duration of their lifespans (or until the compiler is forced to spill them onto the stack), constants may be stored in read-only memory, or any other optimization that the compiler deems fit.

Footnote 1: In functions that use alloca or a VLA, the compiler will use a separate register (like RBP in x86-64) as a "frame pointer" even in an optimized build, and address locals relative to the frame pointer, not the stack pointer. The amount of named C variables is known at compile time, so they can go at the top of the stack frame where the offset from them to the frame pointer is constant. Multiple VLAs can just work as pointers to space allocated as if by alloca. (That's one typical implementation strategy).

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847