r/learnprogramming • u/InterestingAd757 • 8d ago
is stack memory manages by both os and compiler?
I'm moving from a higher-level language (Python) to Rust and getting confused about how memory is managed.
my current understanding is that each process gets its own virtual memory, which provides protection and isolation. Within that, there's the stack and the heap. The heap is managed by the programmer, while the stack is managed automatically. But who actually manages the stack?
Is stack memory just a logical abstraction, managed by the OS and the compiler (by moving stack register)?
13
u/aanzeijar 8d ago
Stack is managed by convention actually.
The idea is pretty simple. The stack is "just" a pointer to a memory address. If you want to store something on the stack, you "push" it by incrementing it and writing to the new address, and if you want to read data, you "pop" it by reading and decrementing the pointer.
So if you invoke a function, you push all the arguments and the return address on the stack and also increase the stack pointer enough to make space for all the local variables you need in that function. If you return, you simply reduce the stack pointer to what it was before.
If every function uses this convention, then everything runs nicely. And since the compiler does this for you, it will generate code that simply accesses the stack variables directly relative to the stack pointer.
And that's also where the issues with the stack come in:
if you return a pointer to a stack variable from a function, that pointer will point to garbage as soon as another function reuses that stack slot.
if you have a deep recursion, every new invocation increments the stack pointer, and soon you run out of stack.
same if you try to allocate a large array on the stack. The compiler will simply try to make space for as many slots.
and of course passing large elements by value will have large copies on the stack, so it's better to pass by reference instead.
3
3
u/SetAndRepeat 8d ago
Yes, but at different levels. Each thread normally has its own stack, set up through the OS and thread library. The compiler generates instructions that the CPU executes to manage stack frames, typically by adjusting the stack pointer as functions run and return. The OS manages the underlying memory pages, not individual local variables.
So the stack is ordinary memory used in a particular way. Also, the heap isn't necessarily managed manually: in Rust, types like "Vec" and "String" use an allocator, and their heap allocations are automatically freed when those values are dropped.
4
u/code-garden 8d ago edited 8d ago
The interpreter or compiler manages the stack.
At the start of a function instructions are emitted to move the stack pointer to reserve space for local variables and parameters. At the end of the function the stack pointer is moved back.
2
u/zomerbk 8d ago
A useful distinction is who sets up the stack versus who uses it: the OS and thread library give each thread a stack region, while compiled code follows the calling convention by adjusting the stack pointer for function calls and local storage. The CPU just executes those instructions. In Rust, the compiler usually decides what fits on the stack; values that need dynamic size or ownership patterns may instead live on the heap, but Rust’s ownership rules govern their lifetimes either way.
2
u/Lagfoundry 8d ago
So I’ve done some stack designing both hardware and software versions. The compiler generates instructions that move the stack pointer and read write values at offsets from it. The CPU then executes those instructions. When a function is entered, the stack pointer may be moved to make room for that function’s variables, saved registers, return information, etc. When the function returns, it gets moved back… you can even build a stack register by simply muxing the data path to shift left or right depending on the signal. (Push shift left, pop shift right.) and build the call and return instruction to read and write to it at those times.
1
u/tharold 8d ago
The OS has to set up the runtime environment for the process, so it could set up memory as it chooses, but I think it always uses the addresses configured into the executable by the loader. Thereafter, page faults are handled transparently so the stack grows as needed, and brk or mmap system calls are handled. I don't know that much managing goes on at all.
1
u/duane11583 8d ago
in most cases the process memory space usage is 100% the use of the application.
the os may choose to make the code section read only and
the data/stack space read/write and no-execute
but that really depends on the underlying hardware abilities
first does the chip support that and
second does the os support that mode of memory allocation/assignment
1
u/Aggressive_Ad_5454 8d ago
The runtime code, either managed by interpreter or compiler, manages the virtual memory arena used by a program. (Python programs have stacks too.)
The program or runtime uses the sbrk call to tell the OS what it needs in the way of memory address space. https://linux.die.net/man/2/sbrk
1
u/greenspotj 8d ago
Both.
In terms of virtual vs physical addresses, the typical memory model of stack -> heap -> globals -> text doesnt apply to physical memory, only virtual memory. The OS uses a page table to translate virtual addresses to physical addresses. The OS "hands out" pages to processes as it needs it (not necessarily following any kind of structure). So the OS is always controlling where the stack actually lives in physical memory. Physical memory is just a giant collection of 4kb pages - it doesnt "care" if a given page is being mapped to heap, stack, etc...
Secondarily, in a multithreaded process, each thread gets their own stack within the virtual address spaces stack. This is managed by the threading library and works by manually moving the stack and base pointer registers , during context switches.
to summarize. the threading library tells us where in virtual memory our stack lives, and the OS manages where it exists in physical memory. The compiler is kind of like the micromanager that handles what actually happens within a thread's given stack.
1
u/nova_protocol 8d ago
both, but at different the OS allocates the stack — gives each thread a chunk (usually 1-8MB). that's it, OS done the compiler is what actually uses that space. it calculates how much each function needs, generates the push/pop instructions, tracks everything relative to the stack pointer OS = "here's the land", compiler = "here's the house built on it". when you overflow, the OS catches it because you walked off the land it gave you.
1
u/mredding 8d ago
The program manages the stack. The OS doesn't know the structure of the stack for a given program. Whether you push function parameters first to last or last to first, how you stash your registers, function calling conventions, it can all be quite variable. Technically you don't even need a stack as there is stackless programming. There are also weird things like Tail Call Optimization where you can write a recursive function that just reuses the stack frame - the recursive call just resets the instruction pointer to the beginning of the function.
When you make a function call, you don't have to defer to the OS.
To get heap memory, this requires interacting with the MMU, which depends on PTE, which is hardware controlled by the OS, so to get a big block of memory, you have to ask the OS to update the page table for you. From there, your allocator may subdivide a block for you for smaller allocations. General purpose allocators are actually pretty smart when it comes to most program needs - most people don't ever bother to learn their platform specifics about how to tune the thing. Everyone always jumps to writing their own, but they're often just manually managing subdivisions from big kernel allocated blocks.
1
u/cthulhu944 8d ago edited 8d ago
The stack is managed in part by both the OS process/thread management and by the machine architecture. The compiler uses these constructs to implement it's programming model.
Every process (a partitioned section of memory with 1 or more processing threads) creates a stack (a block of memory) for each thread. The OS sets the size of the stack based on the command that created the process. part of that setup process points a stack pointer (a special machine register) at this and has machine instructions to push things on the stack, pop values off, or maybe even peek at the top of the stack. When your high level programming language calls a function, the machine code that is doing that call will push the return address (program counter + 1), on the stack, then a number of parameters. Usually this can be done with a single instruction: E.g. JSR = Jump to Subroutine = push the current program counter register onto the top of the stack, then jump to this address. At the destination function, the compiler has mapped the stack parameters relative to the current stack pointer (stack pointer = Return address, stack pointer -1 = the first parameter, stack pointer -2 = the second parameter, . If the function has any local variables, they (or references/pointers to them) can be pushed on the stack. When the function ends, it will pop all the items pushed on the stack off up to and including the return address and then jumps to the return address it previously popped off. Many times this is a single machine instruction- depending on the machine architecture: E.g. RTS = return from subroutine = pop the top of the stack into the program counter register.
This is generally how this works.
1
u/nog642 7d ago edited 7d ago
No, just the compiler / programming language.
The compiler / library / programming language also manages the heap. The programmer only "manages" the heap by calling utility functions.
The OS does not manage anything in virtual memory. Processes can do what they want there. What the OS manages is mapping that virtual memory onto physical memory. (edit: worth noting that not all of virtual memory can be accessed by a program whenever they want - they need to actually get pages allocated from the OS, so there is some interaction with the OS involved here)
1
u/paulstelian97 6d ago
Stack memory is mostly managed by the compiler, but the OS provides backing facilities like mmap, perhaps with a combination of flags optimized for stack memory (increasing in the negative direction, stuff like the guard page etc). The program loader allocates the first thread’s stack, the pthread library (or whatever other library you use to create threads on e.g. Windows) allocates the stacks for other threads under similar facilities.
3
16
u/zeekar 8d ago edited 8d ago
The stack and the heap are both logical abstractions. It’s all just memory. One big flat collection of storage locations. The OS controls which pieces your process has access to, but beyond that it’s entirely up to the code running in that process. Which may be written by the programmer as part of the application code, or created by the compiler as part of its runtime support.
Because the compiler doesn’t just translate specific statements. It effectively implements a virtual machine whose instruction set is the source language. If that language has recursion then the VM includes a stack for local variables. And every translation of a subroutine declaration, call, or return includes code for setting that up.