Memory Layout of C Programs

When a C program is compiled and run, it`s memory is divided into well defined segments. Understanding these segments is essential for debugging, optimizing and avoiding subtle bugs like buffer overflow and segmentation faults.
In this post, we’ll break down the memory layout of a C program and provide examples for each segment.
Overview of Memory Segments
A C program`s memory is typically divided into the following segments:
Text Segment (Code Segment)
Initialized Data Segment
Uninitialized Data Segment (BSS)
Heap Segment
Stack Segment

Let`s explore each segment with code examples.
1. Text Segment (Code Segment)
The text segment (also known as code segment) contains the machine compiled instructions of the program. It is typically read-only to prevent any accidental modifications to it.
#include <stdio.h>
int main() {
return 0;
}
Output:
$ gcc -o memory_layout memory_layout.c
$ size memory_layout
text data bss dec hex filename
1228 544 8 1780 6f4 memory_layout
| text | Size (in bytes) of the text segment which includes all the compiled code. In this case, 1228 bytes are used for instructions |
| data | Size of initialized data segment. Although, there is no data initiaized in the code, the value 544 bytes comes from data initialzed from linked libraries, especially libc |
| bss | Size of uninitialized data segment. Although, there is no data initiaized in the code, the value 8 bytes comes from data initialzed from linked libraries, especially libc |
| dec | Total size in decimal 1228 + 544 + 8 = 1780 bytes |
| hex | Same value as decimal in hexadecimal |
| filename | The name of the compiled binary file |
2. Initialized Data Segment
As the name suggests, this segment store global and static variables with explicit intial values that has been initialized in the program.
#include <stdio.h>
int global_var = 10;
static int static_var = 20;
int main() {
return 0;
}
Output:
$ gcc -o memory_layout memory_layout.c
$ size memory_layout
text data bss dec hex filename
1228 552 8 1788 6fc memory_layout
As seen in the output above: the data segment (initialized data segment) has increased from 544 bytes to 552 bytes whereas the bss segment value remains the same.
3. Uninitialized Data Segment (BSS)
Uninitialized data segment also known as BSS (Block Started by Symbol) contains global and static variables that has not been initialized in the program. These variables are automatically initialized to zero at runtime by the operating system.
#include <stdio.h>
int global_var;
static int static_var;
int main() {
return 0;
}
Output:
$ gcc -o memory_layout memory_layout.c
$ size memory_layout
text data bss dec hex filename
1228 544 16 1788 6fc memory_layout
As seen in the output above: the bss segment has increased from 8 bytes to 16 bytes whereas the data segment (initialized data segment) value remains the same.
4. Heap Segment
Heap segment is used where dynamic memory allocation takes place using malloc(), calloc(), realloc() etc.
#include <stdio.h>
#include <stdlib.h>
int main() {
int* heap_var = (int*)malloc(10 * sizeof(int));
*heap_var = 10;
printf("Heap Var = %d\n", *heap_var);
free(heap_var);
return 0;
}
Output:
$ gcc -o memory_layout memory_layout.c
$ ./memory_layout
Heap Var = 10
5. Stack Segment
Stack segment is responsible for storing local variables, function addresses and function parameters. Each time a function is called, a stack frame is created to store local variables, function parameters and return addresses.
#include <stdio.h>
#include <stdlib.h>
void stack_function() {
int local_variable = 42; // Local variable stored on the stack
printf("Local variable value: %d\n", local_variable);
}
int main() {
stack_function();
return 0;
}
Output:
$ gcc -o memory_layout memory_layout.c
$ ./memory_layout
Local variable value: 42
Common Pitfalls
Understanding how improper use of memory segments can lead to bugs is crucial. Below are some typical pitfalls explained in detail:
1. Stack Overflow
The stack has a fixed size limit (usually few MB on most systems). When a program exceeds this limit, a stack overflow occurs resulting in a segmentation fault.
This commonly happens due to:
Infinite recursion
Allocating large arrays as local variables
void recurse() {
int big_array[100000]; // ~400KB stack allocation
recurse(); // no base case, causes infinite recursion
}
Each call to recurse() allocates a new frame on the stack. As there is no termination condition, the stack keeps growing until it runs out of space.
2. Memory Leak
A memory leak occurs when a program allocates memory on the heap and fails to release it using it free(). This leads to memory being reserved but never resused, eventually exhausting the system`s memory.
int* ptr = (int*)malloc(100); // dynamically allocates 100 bytes
// forgot free(ptr); // memory is never released
In the above code, 100 bytes of memory is allocated on the heap , Since free(ptr) is never called the memory remains reserved until the program exits.
3. Dangling Pointer
A dangling pointer is a pointer which continues to reference a memory location after it has been freed or deallocated. Accessing a memory location through such a pointer results in undefined behavior - it may crash your program or corrupt data.
int* ptr = (int*)malloc(sizeof(int));
free(ptr);
ptr = NULL: // right way of deallocating
*ptr = 10; // undefined behavior : accessing freed memory
In the above code, memory is allocated for int, free(ptr) deallocates the memory. But ptr still points to invalid memory.
Conclusion
Understanding memory layour helps:
write safer code
avoid bugs
optimize embedded systems




