Modern C Programming and Logic Systems
C Memory Model
Where Your Code Lives
When you compile and run a C program, it doesn't just execute randomly in your computer's memory. The operating system organizes it into a clean, predictable structure called a process address space. This space is divided into distinct segments, each with a specific purpose. Understanding this layout is the key to mastering pointers, memory management, and debugging tricky errors.
The Four Segments
Every C program is organized into four primary memory segments. Let's walk through them from the lowest memory address to the highest.
- Text Segment: This is where the compiled machine code of your program resides. It's the collection of instructions the CPU executes. To prevent a program from accidentally or maliciously modifying its own instructions, this segment is typically marked as read-only.
- Data & BSS Segments: These segments store global and static variables, which exist for the entire lifetime of the program. They are split into two sub-sections:
- Initialized Data Segment: Stores global and static variables that have an explicit starting value. For example,
int global_max = 99;would live here. - : Stores global and static variables that are not initialized in the code. For example,
static int counter;. The operating system ensures that all memory in this segment is initialized to zero before your program starts running.
- Heap: This is the region for dynamically allocated memory. When you use functions like
malloc()to request memory while the program is running, that memory is carved out of the heap. The heap starts at the end of the BSS segment and grows upward toward higher memory addresses. Crucially, you are responsible for managing this memory yourself by callingfree()when you're done with it.
- Stack: This segment is used for local variables, function parameters, and return addresses. It operates on a Last-In, First-Out (LIFO) basis. When a function is called, its data is "pushed" onto the stack. When the function finishes, its data is "popped" off. The stack starts at the highest memory address and grows downward.
The Stack in Action
The stack is the workhorse for managing the flow of your program. Every time you call a function, a new is created and pushed onto the stack. This frame is a self-contained block of memory that holds everything the function needs to do its job:
- Its local variables.
- The parameters passed to it.
- The return address—the spot in the code it needs to jump back to when it's finished.
When the function completes, its entire frame is popped off the stack, and all its local variables are destroyed. This automatic allocation and deallocation is what makes the stack fast and efficient.
This frame-based system explains why recursive functions can be dangerous. Each recursive call pushes a new stack frame. If the recursion goes too deep without a base case to stop it, the stack runs out of space. This collision with the heap or other memory segments results in a classic crash: a stack overflow.
| Feature | Stack | Heap |
|---|---|---|
| Allocation | Automatic (by compiler) | Manual (by programmer) |
| Deallocation | Automatic (on function return) | Manual (must call free()) |
| Lifespan | Exists only during function call | Persists until manually freed |
| Size | Fixed, relatively small | Flexible, much larger |
| Access Speed | Very fast (CPU pointer manipulation) | Slower (more complex management) |
In which memory segment are uninitialized global and static variables stored?
What is the primary difference in how memory is managed on the Stack versus the Heap?
