C Memory Layout — Stack, Heap, Data Segment, and Text Segment
In this tutorial, you will learn about C Memory Layout. We cover key concepts, practical examples, and best practices to help you master this topic.
C programs are organized into four memory segments: text segment (program code), data segment (global and static variables), stack (local variables and function calls), and heap (dynamically allocated memory).
Why It Matters
Understanding memory layout helps you write efficient code, debug crashes, and avoid common memory issues. Stack overflow, segmentation faults, and memory fragmentation all relate to how the program uses memory segments. Knowing which data goes where helps you make informed decisions about allocation strategies.
Real-World Use
Embedded Systems must understand memory layout because they have limited RAM and ROM. Stack sizes must be configured for each thread in multithreaded programs. Buffer overflow exploits target specific memory segments. Durga Antivirus Pro's memory scanner checks for malicious code in each segment.
What You Will Learn
- The four memory segments and their purposes
- Stack vs heap allocation tradeoffs
- Data segment: initialized vs uninitialized (BSS)
- Text segment: read-only code
- How function calls use the stack
Learning Path
flowchart LR A[Dynamic Memory] --> B[Memory Layout
You are here] B --> C[Structs] C --> D[Unions] D --> E[Bit Fields] style B fill:#f90,color:#fff
The Four Memory Segments
flowchart LR
subgraph "Process Memory"
TS[Text Segment
Code, read-only]
DS[Data Segment
Globals, statics]
HEAP[Heap
Dynamic allocation]
STACK[Stack
Local variables]
end
When a C program runs, the operating system allocates address space divided into these segments:
| Segment | Contents | Access | Growth |
|---|---|---|---|
| Text | Program instructions | Read-only | Fixed |
| Data | Global/static variables | Read-write | Fixed |
| BSS | Uninitialized globals | Read-write | Fixed |
| Heap | malloc/calloc/realloc | Read-write | Grows upward |
| Stack | Local variables, call frames | Read-write | Grows downward |
The Stack
The stack stores local variables and function call information. It grows and shrinks automatically as functions are called and return:
#include <stdio.h>
void func2() {
int local = 20; // On stack
printf("func2: local at %p\n", &local);
}
void func1() {
int local = 10; // On stack
printf("func1: local at %p\n", &local);
func2();
}
int main() {
int main_local = 0; // On stack
printf("main: local at %p\n", &main_local);
func1();
return 0;
}
Expected output (addresses will vary, but addresses decrease as we go deeper):
main: local at 0x7fff12345678
func1: local at 0x7fff12345654
func2: local at 0x7fff12345634
Stack Characteristics
- Fast: allocation and deallocation are just pointer adjustments
- Automatic: variables are created when entering a block, destroyed when leaving
- Size-limited: typically 8 MB on Linux, 2 MB on Windows
- LIFO: Last-In-First-Out order
- Thread-local: each thread has its own stack
Stack Overflow
#include <stdio.h>
void recurse(int depth) {
char buffer[1024]; // Each call uses 1 KB of stack
printf("Depth: %d\n", depth);
recurse(depth + 1); // Eventually overflows the stack
}
int main() {
recurse(1);
return 0;
}
This program will crash with a stack overflow when it runs out of stack space. The exact depth depends on the stack size limit.
The Heap
The heap is managed by malloc/free and grows upward (toward higher addresses):
#include <stdio.h>
#include <stdlib.h>
int main() {
int stack_var = 0;
int *heap1 = malloc(sizeof(int));
int *heap2 = malloc(sizeof(int));
int *heap3 = malloc(sizeof(int));
printf("Stack variable: %p\n", &stack_var);
printf("Heap allocation 1: %p\n", heap1);
printf("Heap allocation 2: %p\n", heap2);
printf("Heap allocation 3: %p\n", heap3);
// Heap addresses are typically lower than stack addresses
// and increase with each allocation
free(heap1);
free(heap2);
free(heap3);
return 0;
}
Heap Characteristics
- Slower: malloc/free involve system calls and bookkeeping
- Manual: you must explicitly allocate and free
- Large: limited by available RAM (not stack size)
- Flexible: can resize with realloc
- Shared: accessible from any function (pass the pointer)
The Data Segment
The data segment stores global and static variables:
#include <stdio.h>
// Data segment: initialized global
int global_var = 42;
// BSS: uninitialized global (zero-initialized at startup)
int uninitialized_global;
// Data segment: initialized static
static int static_var = 100;
// BSS: uninitialized static
static int uninitialized_static;
int main() {
// String literal in text segment (read-only)
const char *str = "Hello";
printf("global_var: %p (%d)\n", &global_var, global_var);
printf("uninit_global: %p (%d)\n", &uninitialized_global, uninitialized_global);
printf("static_var: %p (%d)\n", &static_var, static_var);
printf("uninit_static: %p (%d)\n", &uninitialized_static, uninitialized_static);
printf("string literal: %p (%s)\n", str, str);
return 0;
}
Data Segment Subdivisions
- Initialized data: global and static variables with initial values
- BSS (Block Started by Symbol): global and static variables without explicit initializers (zeroed at program start)
- Read-only data: string literals and const-qualified global data
The Text Segment
The text segment contains the actual machine code instructions. It is typically read-only:
#include <stdio.h>
void function() {
printf("Function code is in text segment.\n");
}
int main() {
printf("main function address: %p\n", main);
printf("function address: %p\n", function);
// Text segment is read-only
// *(char*)main = 0x90; // Would cause segmentation fault
return 0;
}
Text Segment Characteristics
- Read-only: prevents accidental modification of code
- Shared: multiple instances of the same program share the same text segment
- Fixed size: code size is determined at compile time
- Executable: the CPU fetches instructions from this segment
Visualizing Memory Layout
#include <stdio.h>
#include <stdlib.h>
// Data segment
int global = 10;
static int s_global = 20;
// BSS
int uninit_global;
int main() {
// Stack
int local = 30;
static int s_local = 40; // Actually in data segment!
// Heap
int *heap = malloc(sizeof(int));
*heap = 50;
printf("Code (main): %p\n", main);
printf("String literal: %p\n", "test");
printf("Initialized: %p (global=%d, s_global=%d, s_local=%d)\n",
&global, global, s_global, s_local);
printf("BSS: %p (uninit=%d)\n", &uninit_global, uninit_global);
printf("Heap: %p (value=%d)\n", heap, *heap);
printf("Stack: %p (local=%d)\n", &local, local);
// Typical relationship: code < data < heap < stack
// (addresses increase from code to data to heap to stack)
free(heap);
return 0;
}
Stack Frame Structure
Each function call creates a stack frame containing:
- Return address: where to resume execution after the function returns
- Saved frame pointer: previous function's base pointer
- Local variables: space for the function's local variables
- Saved registers: registers that must be restored before returning
#include <stdio.h>
void add_and_print(int a, int b) {
int result = a + b; // Local variable in stack frame
printf("Result: %d\n", result);
}
int main() {
int x = 5, y = 3; // In main's stack frame
add_and_print(x, y); // Creates a new stack frame
return 0;
}
Common Mistakes
1. Stack Overflow with Deep Recursion
void infinite() {
infinite(); // Each call consumes stack space
}
Use iteration instead of recursion for unbounded depth, or increase the stack size.
2. Returning Address of Stack Variable
int *bad() {
int x = 42;
return &x; // x is destroyed when function returns!
}
Allocate on heap or use static storage.
3. Assuming Large Stack Size for Local Arrays
void process() {
int huge[1000000]; // 4 MB on stack -- may overflow
}
Use malloc for large arrays.
4. Writing to Code Segment
*(char*)main = 0x90; // Segmentation fault
Modern operating systems mark the code segment as read-only.
5. Not Understanding Static Storage Duration
void counter() {
static int count = 0; // Initialized once, in data segment
count++;
printf("%d ", count);
}
// Output: 1 2 3 ... on successive calls
Practice Questions
What is stored in the text segment? The program's machine code instructions. It is read-only and executable.
What is the difference between stack and heap allocation? Stack: fast, automatic, limited size. Heap: slower, manual, large capacity.
Where are global variables stored? In the data segment (initialized) or BSS (uninitialized).
What causes a stack overflow? Excessive function calls (deep recursion) or large local arrays that exceed the stack size limit.
Challenge: Write a program that prints the approximate addresses of code, data, heap, and stack to visualize the memory layout of your system.
Mini Project: Memory Layout Visualization
#include <stdio.h>
#include <stdlib.h>
int global_init = 100;
int global_uninit;
static int static_var = 200;
int main() {
int stack_var = 300;
int *heap_var = malloc(sizeof(int));
*heap_var = 400;
printf("Memory Layout:\n");
printf("================\n");
printf("Text (code): %p\n", main);
printf("Data (init): %p\n", &global_init);
printf("Data (static):%p\n", &static_var);
printf("BSS (uninit):%p\n", &global_uninit);
printf("Heap (malloc):%p\n", heap_var);
printf("Stack (local): %p\n", &stack_var);
printf("================\n");
printf("Strings: %p\n", "hello");
free(heap_var);
return 0;
}
FAQ
What is Next
Now that you understand memory layout, proceed to Structs to learn about defining custom data types that group related variables.