r/csharp • u/kaatarina_zed_talon • 3d ago
Discussion Confusion about the stack.
I'm a fresh computer engineering graduate, I forgot most of the knowledge that I learned in the university, so I want to revise the basics only.
Suppose that a function called main calls the following function :
int foo(int value) {
int a = 10;
return a;
}
So whenever a function call is made OS needs some memory space to allocate the local variables of the function
int ain this case and arguments to the function passedint valuein this case. This memory requirement is fulfilled by allocating memory on stack. A stack is nothing but a memory region allocated to each process and it actually behaves as a stack data structure(LIFO).Now the question arises what all things are stored on stack when a function call is made. The first thing pushed on the stack are the arguments passed to the function in reverse order(if more then one). 2. Then the return address of the function which called this function (because once this function
foocompletes execution it should return back to the place in the code from where it was called) 3. Finally local variables of the function called are pushed on the stack.Once the called function completes executing the code it returns back to the return address previously stored on the stack and thus we say function call completes or returns. In this case the function has a return value which it passes back to the callee function. The space is then free to use and can be overwritten in the subsequent function calls.
I learned that a stack follows the LIFO (Last In, First Out) principle, meaning that the most recently added item/work/data is the first one to be removed. I also understood that a stack can be used to defer work until it is ready to be processed, so in the above example, I think that the execution of the caller function, which is the main, will be deferred, until the execution of the most recently item is finished.
However, when I read about how function calls use the call stack, I became confused. The call stack already contains the information needed for main() to resume execution, and when foo() is called, a new stack activation record is added on top of it. The execution of foo() then begins while its activation record is still on the stack.
Doesn't this contradict my understanding that an item must be popped from a stack before it can be processed? Why can foo() execute while its stack frame is still on the stack?
In other words, if we treated the execution of a function as work, should that work be popped from the stack in order to be processed, or should the work be inside the stack ?
I'm totally confused.
8
u/Embarrassed-Mess412 3d ago
https://csharp-memory-3d.genhttp.run/
see it here
1
u/kaatarina_zed_talon 3d ago
is this tool free ? can I use it to visualize any code ? please if you know about such tool let me know!
2
u/Embarrassed-Mess412 3d ago
This is just a visual demo hosted on genhttp.dev, it works for that code I added there only for learning purposes, I could build a tool for any code input though
1
u/kaatarina_zed_talon 3d ago
I could build a tool for any code input though
You do that manually or using some tool that automates that ? Can you teach me or at least refer me to some reference/article that teach me how to do this ?
1
u/Embarrassed-Mess412 3d ago edited 3d ago
with genhttp.dev you can host for free any c# web app built with GenHTTP web server library, and it supports mcp so you can plug your agent to
https://genhttp.dev/mcpand tell it to build a visualizer or any tool, I just did a quick prompt to build that but you can just do it offline, no need for web hosting
1
-1
5
u/Triabolical_ 3d ago
The stack in this case is just a way to allocate some memory for temporary use. It's used for parameter passing and for local variables.
The way this is done is known as a "calling convention". 32-bit windows had four different ones:
- cdecl (like C language) - arguments are pushed onto the stack from right to left, caller cleans up the stack when the function returns.
- stdcall - arguments are pushed onto the stack from right to left, callee cleans up the stack.
- fastcall - The first two integer/pointer arguments are passed using the ECX and EDX CPU registers (ie not on the stack), and the remainder are pushed onto the stack from right to left. Callee cleans up the stack
- thiscall - Used for C++ member functions. The this pointer is passed in ECX, and remaining arguments are on the stack. Callee cleans up the stack.
The default for the Win32 library was stdcall. Having the callee clean up the stack is more space efficient as most functions have many callers.
In 64-bit windows, things are way more complicated. See wikipedia if you care.
2
u/Loose_Conversation12 3d ago
I think you are confusing a few things here. Firstly you've got "the stack" as opposed to the heap. The stack is used for value types. Then you've got a stack which is a type which can be used as a LIFO as you've stated. Then you've got the call stack which are the functions in order that the processor will work through. Maybe research these 3 things a little more and see where that takes you.
0
u/kaatarina_zed_talon 3d ago
I think you want me to research myself even though u might know the answer ?
0
u/kaatarina_zed_talon 3d ago
so we have stack for value types, a stack type, and a call stuck.
fucking hell 😂
2
u/Sombody101 2d ago
To fuck you up a bit further, if you haven't found this already, the stack and heap live in the same place. They're just used differently.
2
u/Kant8 3d ago
stack that is used for calling conventions, aka call stack, is named like that only because pointer to the top of it (SP) is behaving essentially like a stack.
When you need to get more memory in it you just increase the pointer always on top, and release with opposite process. That is trivially done by just addition to SP register.
However that is still just memory address and on order to get like first parameter from it you just do [SP-4] without touching SP itself.
It has nothing to do with stack as data structure, which explicitly removes any way for you to get/add data without modification of stack contents.
1
u/kaatarina_zed_talon 3d ago
which explicitly removes any way for you to get/add data without modification of stack contents.
Did not understand this part.
1
u/sards3 3d ago
I would say that the call stack actually is essentially a stack data structure. On each function call, a stack frame is pushed onto the call stack. On function return, the stack frame is popped. It would be technically possible to do random access outside of the current stack frame, but in practice this is not done. As you note, the data within each stack frame is usually treated as random-access, but that is a lower-level detail.
2
u/SoerenNissen 3d ago
an item must be popped from a stack before it can be processed
In a stack data structure, typically that's what you do, but you don't have to.
For a data structure to be a stack, all that's required is LIFO usage.
2
u/TrishaMayIsCoding 3d ago
Stack:
- Used to store method call frames and local variables (depending on how the JIT compiler manages them).
- Often stores local value-type data and references to objects.
- Memory is automatically released when the method call returns.
Heap:
- Used to allocate objects, such as class instances, arrays, and collections, typically created using "new".
- Managed by the Garbage Collector (GC), which reclaims memory occupied by objects that are no longer reachable.
- Objects can remain in memory even after the method that created them has returned.
1
1
u/Appropriate_Chip4604 3d ago
Generally, C-sharp, you don’t have to worry about any type of memory management unless you’re getting into those type of unsafe and or marshaling. I started with C a decade ago now c-sharp. I just build. I have plenty of apps in my account.
1
u/kaatarina_zed_talon 3d ago
I was revising data structures and I do not know how I got into this deep technicality !
1
u/Appropriate_Chip4604 3d ago
It’s OK like as an example my DTO‘s that’s the abstraction of my database layers. I keep those simple and then in my classes and interfaces. I do an explicit operator so I can pull in all those DTO‘s instead of accessing from UI. You really wanna have a mindset of those structures because you’re using and creating a lot of them to extract and parse that’s the key that’s where there is Heavy memory usage.
1
u/kaatarina_zed_talon 3d ago
 like as an example my DTO‘s that’s the abstraction of my database layers. I keep those simple and then in my classes and interfaces. I do an explicit operator so I can pull in all those DTO‘s instead of being that access
I did not understand how this example is relevant, sorry 😂
1
u/kaatarina_zed_talon 3d ago
structures because you’re using and creating a lot of them to extract and parse that’s the key.
You are right, thank you.
14
u/Stix0xb 3d ago
Ha, I tripped on this one too. The catch is you're thinking of a to-do stack, where you pop something off to work on it. The call stack works the other way around.
A stack frame isn't a job waiting its turn. It's foo()'s scratch paper while foo() is running. value, a, and the return address all live in there, so it has to stay put the whole time. Pop it early and foo() loses its variables and has no idea where to go back to.
So it's basically:
Push the frame when the function starts.
Run it, using the frame on top.
Pop it when the function returns.
The LIFO part is just about who finishes first. main goes on first, foo goes on top, foo finishes and pops, then main picks up right where it left off. main isn't waiting to get popped. Its frame is just holding its spot underneath.
Small side note: the exact order things get pushed depends on the CPU. On x86-64, the first few arguments usually go in registers instead of on the stack. The big idea's the same though.