Today, we’ll talk about three key parts of our computer’s memory: the stack, the heap, and the buffer.
Stack:
To truly understand how applications run, it’s essential to recognize the vital role the stack plays in their routine operations
But what is a stack???
A stack is a simple data structure that basically stacks data on top of each other element and has elements added or removed.
But you would ask, how are the elements added or removed?
One of the most used way is called LIFO (Last-in First-out ), when you add to the stack, you have to push
it onto the stack, and when you want to remove it, you pop
it out of the stack, also we have FIFO (First-in first-out)
Quite simple, huh?
Push = add, and pop = remove.
But what makes the stack so important???
The first thing is that all local variables are stored on it, like a temporary storage , they have their space allocated on the stack.
We allocate the space by substracting/adding a number from the current stack pointer(ESP - which points to the top of the stack).
Another thing we should know is that the top of the stack is the lowest currently in use address of the stack which means that the stack is growing down (IA-32).
What about the bottom of it??? The bottom is bounded by the buttom of the current stack frame and is pointed by the EBP.
You may ask, what is this thing called a stack frame? So, we should break it down.
A stack frame
is a section of memory that contains information about function that we call in a program, which stores important data as parameters
, local variables
and ret address
.
Now that we have that image, we can understand that the stack frame is the current view in the stack, relevant to the currently executing function.
Another important detail we should remember is that the stack frame is constructed during the procedure prologue and destroyed during the procedure epilogue.
int c = 15;
std::cout << "In main: c = " << c << std::endl;
In assembly would be(visual studio dissambly):
mov esi,esp
push offset std::endl<char,std::char_traits<char> >
mov edi,esp
mov eax,dword ptr [c]
push eax
push offset string "In main: c = " (07E9BFCh)
mov ecx,dword ptr [__imp_std::cout (07ED0D8h)]
push ecx
call std::operator<<<std::char_traits<char> > (07E11B3h)
mov eax,dword ptr [c]
-this instruction loads the value of the local variable c into the eax register. The local variable c is likely stored on the stack.
But what happens if we use IDA?
mov [ebp+var_8], 0Fh
stores the value 15 (0Fh) at the stack location determined by ebp + var_8. The ebp register marks the base of the current stack frame, and this instruction updates a local variable on the stack.
Not to forget the prologue of our code.
push ebp
mov ebp, esp
sub esp, 0CCh
Before calling the cout function, we first push the value in eax (which contains the local variable c), followed by pushing the string literal. This order aligns with the calling convention, which specifies that arguments are pushed onto the stack in reverse order (right-to-left). Therefore, the last argument pushed (the string) will be the first one accessed by the function, ensuring that the parameters are correctly received (ds - data segment)
mov eax, [ebp+var_8]
push eax
push offset Str ; "In main: c = "
mov ecx, ds:?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A ; std::ostream std::cout
The procedure prologue
push ebp
mov ebp, esp
sub esp, 0x123
The procedure epilogue
mov esp, ebp
pop ebp
ret
The Heap
The heap is another crucial data structure due to its frequent and extensive use.
The heap in simple words is a section of memory that is used for dynamically allocated variables that should exist outside of the current stack frame.
that should exist outside of the current stack frame
??????!!!
So , what does it mean?? In the case of a stack frame, it is created for a function. Any local variables withing that function are stored on the stack and will be removed once the functions exits.
The difference is that in the heap, the variables are allocated dynamically and they remain in the memorry until they’re explicitly freed. This allows them to persist beyond the function that created them, making them accessible from different parts of the program as needed.
At the initialization, the heap will request a fairly large section of memory from the OS, and will hand out smaller chunks of memory based upon requests from the application. These chunks will typically have inline medata indicated the cunk’s size and other elements, such as the size of the previous block of memory.
The blocks of allocated memory are navigated by taking the pointer of a given chunk and adding its size to find the next chunk, or by subtracting it to find the previous one.
Memory managemnt in heap involves both dynamic allocation and deallocation of memory. -Dynamic Allocation: Memory is allocated at runtime using functions like malloc() in C++. This allows for flexible memory usage when the size of data structe is not known at compile time.
int *array = (int *)malloc(10 * sizeof(int)); // Allocating memory for an array of 10 integers
-Deallocation:
Unlike the stack, where memory is automatically reclaimed, heap memory must be explicity freed using free()
in C or delete
in C++.
free(array);
Fragmentation in the Heap
Fragmentation refers to how memory can become inefficiently utilized over time as a consequence of dynamic memory management practices. There are two main types:
-
Internal Fragmentation:
- This occurs when allocated memory blocks are larger than necessary, resulting in wasted space.
- For example, if you allocate a block of 64 bytes but only use 50 bytes, the remaining 14 bytes are wasted within that block.
-
External Fragmentation:
- This happens when free memory is scattered in small blocks, making it difficult to allocate larger chunks even when the total free memory is sufficient.
- Example:
- You allocate a block of 100 bytes.
- Then you allocate another block of 50 bytes.
- If you free the first block, you might have 100 bytes available, but if you then request 120 bytes, you may fail to allocate it due to fragmentation.
Buffer
A buffer is a temporary storage area typically used to hold data while it is being transfered between two places, such as between an application and a deice. Buffer may help improve performance by allowing to be processed in chucks, rather than one piece at time.
#include <iostream>
#include <cstring>
int main() {
const int BUFFER_SIZE = 256; // Define the size of the buffer
char buffer[BUFFER_SIZE]; // Create a buffer
std::cout << "Enter some text (up to 255 characters): ";
std::cin.getline(buffer, BUFFER_SIZE); // Read input into the buffer
std::cout << "You entered: " << buffer << std::endl;
return 0;
}
In other words, a buffer is defined as a limited , allocated set of memory.
For the end one major concern with buffers is the risk of buffer overflow, a vulnerability that occurs when data exceeds the buffer’s allocated size. This can lead to unexpected behavior, data corruption, and security vulnerabilities, as attackers might exploit these overflows to execute arbitrary code or gain unauthorized access to system resources. :)
Would you like to support me?
If you find my content helpful and would like to support my work, consider visiting my Patreon page.
Your support means the world to me and helps keep this work going!