Memory Layout in C - Understanding Stack, Heap, and Segments
Understanding Memory Layout
Before you can master pointers or dynamic memory, you need to understand how memory is organized in a running C program. C gives you a level of control that few languages allow, but to use it safely, you must know where your data lives and how long it stays there.
The Memory Segments
When a program runs, its memory is divided into several key sections:
-
Code (Text)
-
Purpose: Stores compiled machine instructions
-
Example Data: Functions, program logic
-
Data (Static)
-
Purpose: Stores global and static variables with initialized values
-
Example Data:
int count = 5; -
BSS (Uninitialized Data)
-
Purpose: Holds global/static variables that start as zero or are uninitialized
-
Example Data:
int buffer[256]; -
Heap
-
Purpose: Used for dynamic memory allocation (
malloc,calloc) -
Example Data: Large, runtime-created data
-
Stack
-
Purpose: Stores local variables, function parameters, return addresses
-
Example Data: Function calls, recursion
These segments are managed differently by the operating system, and each has a different lifetime and scope.
Tiny Code
Here’s a small program that prints the memory addresses of different variables to show where they live:
Code Example
Here is a simple C script to illustrate that point:
#include <stdio.h>
#include <stdlib.h>
// Global variable (Data segment)
int global_var = 42;
// Uninitialized global variable (BSS segment)
int global_bss;
void show_addresses(void) {
// Local variable (Stack)
int local_var = 10;
// Static variable (Data segment)
static int static_var = 20;
// Dynamic variable (Heap)
int *heap_var = malloc(sizeof(int));
*heap_var = 30;
printf("Code (function) address: %p\n", (void *)show_addresses);
printf("Global variable address: %p\n", (void *)&global_var);
printf("Uninitialized global address:%p\n", (void *)&global_bss);
printf("Static variable address: %p\n", (void *)&static_var);
printf("Stack variable address: %p\n", (void *)&local_var);
printf("Heap variable address: %p\n", (void *)heap_var);
free(heap_var);
}
int main(void) {
show_addresses();
return 0;
}
Output and Segment Details
Compile and run:
Code (function) address: 0x561ce7348169
Global variable address: 0x561ce7546014
Uninitialized global address:0x561ce7546018
Static variable address: 0x561ce7546020
Stack variable address: 0x7ffc94b65a5c
Heap variable address: 0x561ce774b2a0
Notice how the stack address is much higher than the heap, the stack usually grows downward, and the heap grows upward in memory.
How It All Works
-
1. Code segment
-
Contains compiled instructions.
-
Usually marked as read-only to prevent accidental modification.
-
2. Data segment
-
Holds global and static variables initialized with values.
-
Exists for the entire program duration.
-
3. BSS (Block Started by Symbol)
-
Holds uninitialized global/static variables.
-
Automatically zero-initialized at runtime.
-
4. Stack
-
Used for local variables and function calls.
-
Automatically managed, grows and shrinks as functions are called and return.
-
5. Heap
-
Allocated manually at runtime.
-
Requires explicit management (
mallocandfree).
Syntax explanation
printf("Code (function) address: %p\n", (void *)show_addresses);
printf("Global variable address: %p\n", (void *)&global_var);
printf("Uninitialized global address: %p\n", (void *)&global_bss);
printf("Static variable address: %p\n", (void *)&static_var);
printf("Stack variable address: %p\n", (void *)&local_var);
printf("Heap variable address: %p\n", (void *)heap_var);
This code is used to print the memory addresses of different types of variables and functions in C. It helps you visualize where different elements of your program live in the system’s memory layout (like the Stack, the Heap, or the Code segment).
Here is a breakdown of the syntax elements used in these lines.
- The %p Format Specifier
- In printf, %p is the standard format specifier used to print a pointer value (a memory address).
- It tells printf to format the internal representation of a pointer into a readable hexadecimal string (for example, 0x7ffee3b4a7bc).
- The (void *) Cast
- You will notice that every variable or function pointer is preceded by (void *). This is an explicit type cast.
- In C, the %p specifier strictly expects a pointer to void (void *), which is a generic pointer type.
- While many compilers will let you get away without it, explicitly casting to (void *) ensures strict compliance with the C standard and prevents compiler warnings across different architectures.
- The Address-of Operator (&)
- Why do some variables have an ampersand (&) before them, while others do not?
- &global_var, &global_bss, &static_var, &local_var: The & operator fetches the memory address of a standard variable. Because these are regular variables, you must explicitly ask for their location in memory.
- show_addresses: There is no & here because show_addresses is a function. In C, the name of a function automatically evaluates to its starting address in memory.
- heap_var: There is no & here because heap_var is already a pointer variable (likely created using malloc). It already holds a memory address as its value, so you pass the variable directly. If you used &heap_var, you would accidentally print the address of the pointer itself, not the memory it points to on the heap.
Summary of Memory Segments Being Printed
Each line targets a completely different region of the program’s memory:
- Code (Text) Segment: (void *)show_addresses points to the read-only area where the compiled machine instructions reside.
- Data Segment (Initialized): (void *)&global_var and (void *)&static_var point to the region where global and static variables that have an initial value are stored.
- BSS Segment (Uninitialized): (void *)&global_bss points to the region for global variables that are not initialized in the code (they default to zero).
- The Stack: (void *)&local_var points to the short-lived, fast-allocating memory region used for local function variables.
- The Heap: (void *)heap_var points to the dynamically allocated memory region managed by malloc or calloc.
Diagram
High Addresses 0xFFFFFFFF
┌────────────────────────────────────────────────────────┐
│ STACK │
│ grows DOWNWARD ↓ (Each new frame = lower address) │
├────────────────────────────────────────────────────────┤
│ │ │
│ ▼ │
│ │
│ ▲ │
│ │ │
├────────────────────────────────────────────────────────┤
│ HEAP │
│ grows UPWARD ↑ (Each new malloc = higher address) │
├────────────────────────────────────────────────────────┤
│ BSS │
│ (Uninitialized global & static variables) │
├────────────────────────────────────────────────────────┤
│ DATA │
│ (Initialized global & static variables) │
├────────────────────────────────────────────────────────┤
│ TEXT │
│ (Compiled machine code instructions) │
└────────────────────────────────────────────────────────┘
Low Addresses 0x00000000