Lesson 38
Memory Layout
Memory Layout in C describes how a program's memory is organized at runtime into distinct regions: the text segment for machine instructions, the data segment for initialized globals, the BSS segment for uninitialized globals, the heap for dynamic allocation, and the stack for local variables and function calls. Understanding these regions helps write efficient, crash-free programs.
C Track
Save progress as you learn.
44 lessons
Lesson content
Memory Layout in C Programming: A Complete Beginner to Advanced Guide
What is Memory Layout in C Programming?
Memory Layout in C programming describes how a program's memory is organized while it is running. Every C program is divided into different memory regions, and each region has a specific purpose.
Imagine a large office building where each floor has a dedicated function. One floor stores important documents, another is used for daily work, another is a warehouse, and another is a meeting room. Similarly, a C program divides memory into separate sections so that code and data can be managed efficiently.
Why Does Memory Layout Exist?
A program needs different types of memory for different purposes: machine instructions, global variables, local variables, dynamic memory allocation, and constants. Separating these into different regions improves organization, performance, and memory management.
Real-World Use Cases
Understanding memory layout is useful in:
- Embedded systems
- Operating system development
- Compiler design
- Debugging applications
- Performance optimization
- Memory leak detection
- Reverse engineering
- Security analysis
Prerequisites
Before learning Memory Layout in C, you should understand:
- Variables
- Data types
- Functions
- Pointers
- Arrays
- Dynamic memory allocation with malloc() and free()
- Storage classes such as static and extern (helpful)
Core Concepts
Overview of Program Memory
A typical C program's memory is divided into the following regions from top to bottom: command-line arguments and environment variables, the stack (local variables and function calls), free memory, the heap (dynamic allocation), the BSS segment (uninitialized globals and statics), the data segment (initialized globals and statics), and the text segment (program instructions and read-only constants).
Text Segment (Code Segment)
The text segment contains the compiled machine instructions of your program. It is usually read-only, shared between multiple instances of the same program, and cannot normally be modified during execution.
void display()
{
printf("Hello");
}The compiled instructions for display() are stored in the text segment.
Data Segment
The data segment stores initialized global and static variables. It is allocated before the program starts, exists until the program terminates, and is read-write memory.
int globalValue = 100;
static int count = 5;BSS Segment
The BSS (Block Started by Symbol) segment stores uninitialized global and static variables. The C language guarantees these variables are initialized to 0 before main() begins. It occupies no space in the executable file for zero-initialized data and exists throughout program execution.
int total;
static int counter;Heap Memory
The heap is used for dynamic memory allocation. Memory is allocated using malloc(), calloc(), or realloc(), and released using free(). It is managed manually by the programmer, is slower than stack allocation, and is suitable for data whose size is determined at runtime.
Stack Memory
The stack stores local variables, function parameters, return addresses, and saved registers. Every function call creates a new stack frame. It is managed automatically, very fast, limited in size, and typically grows downward on many systems.
Heap vs Stack
| Feature | Stack | Heap |
|---|---|---|
| Allocation | Automatic | Manual (malloc()) |
| Deallocation | Automatic | free() |
| Speed | Faster | Slower |
| Size | Limited | Larger (system dependent) |
| Lifetime | Function scope | Until explicitly freed |
| Common Use | Local variables | Dynamic objects |
Code Examples
Example 1: Beginner – Global and Local Variables
#include <stdio.h> // Include standard input/output library
int globalVar = 100; // Stored in the data segment
int main()
{
int localVar = 50; // Stored on the stack
printf("Global = %d\n", globalVar); // Print global variable
printf("Local = %d\n", localVar); // Print local variable
return 0; // End program
}Output: Global = 100 / Local = 50
Example 2: Beginner – Static Variable
#include <stdio.h> // Include standard input/output library
void counter()
{
static int count = 0; // Stored in the data segment
count++; // Increment value
printf("%d\n", count); // Print current value
}
int main()
{
counter(); // First call
counter(); // Second call
counter(); // Third call
return 0; // End program
}Output: 1 / 2 / 3
Example 3: Intermediate – Dynamic Memory Allocation
#include <stdio.h> // Include standard input/output library
#include <stdlib.h> // Include memory allocation functions
int main()
{
int *ptr = (int *)malloc(sizeof(int)); // Allocate memory on the heap
if(ptr == NULL) // Check allocation success
{
printf("Memory allocation failed.\n"); // Display error
return 1; // Exit program
}
*ptr = 200; // Store value
printf("%d\n", *ptr); // Print value
free(ptr); // Release heap memory
ptr = NULL; // Avoid dangling pointer
return 0; // End program
}Output: 200
Example 4: Intermediate – Stack Frame Demonstration
#include <stdio.h> // Include standard input/output library
void display()
{
int number = 25; // Stored in this function's stack frame
printf("%d\n", number); // Print local variable
}
int main()
{
display(); // Call function
return 0; // End program
}Output: 25. Each call to display() creates a new stack frame. When the function returns, its local variables are automatically destroyed.
Example 5: Advanced – Displaying Memory Addresses
#include <stdio.h> // Include standard input/output library
#include <stdlib.h> // Include memory allocation functions
int globalVar = 100; // Global variable
int main()
{
int localVar = 50; // Local variable
int *heapVar = (int *)malloc(sizeof(int)); // Heap allocation
if(heapVar == NULL) // Check allocation success
{
printf("Memory allocation failed.\n"); // Display error
return 1; // Exit program
}
*heapVar = 200; // Store value
printf("Global : %p\n", (void *)&globalVar); // Address of global variable
printf("Local : %p\n", (void *)&localVar); // Address of local variable
printf("Heap : %p\n", (void *)heapVar); // Address of heap memory
free(heapVar); // Release memory
heapVar = NULL; // Prevent dangling pointer
return 0; // End program
}Sample Output: Global: 0x55f6... / Local: 0x7ffd... / Heap: 0x55f7... — actual addresses vary each run due to Address Space Layout Randomization (ASLR).
Common Mistakes and Pitfalls
| Wrong | Correct |
|---|---|
| Forgetting free(ptr) | Free dynamically allocated memory when no longer needed |
| Returning the address of a local variable | Return dynamically allocated memory or use an output parameter instead |
| Using a pointer after free() | Set the pointer to NULL after freeing it if it will be reused |
| Dereferencing a NULL pointer | Check the pointer before dereferencing |
| Assuming stack variables persist after a function returns | Use static or dynamic allocation if longer lifetime is required |
Wrong:
int *func()
{
int value = 10;
return &value;
}Correct:
#include <stdlib.h>
int *func()
{
int *value = (int *)malloc(sizeof(int));
if(value != NULL)
{
*value = 10;
}
return value;
}Remember to call free() on the returned pointer when it is no longer needed.
Best Practices
- Always free dynamically allocated memory.
- Check the return value of malloc(), calloc(), and realloc().
- Set pointers to NULL after calling free() when appropriate.
- Keep stack usage reasonable to avoid stack overflow.
- Prefer stack allocation for small, short-lived objects.
- Use heap allocation only when data must outlive the current function or its size is known only at runtime.
- Avoid returning pointers to local variables.
- Use debugging tools to detect memory leaks and invalid memory accesses.
When NOT to Use This
Avoid heap allocation when:
- The data is small and only needed within the current function.
- Automatic (stack) allocation is sufficient.
- Frequent allocations and deallocations would introduce unnecessary overhead.
- Deterministic timing is required in some embedded or real-time systems, where dynamic allocation may be restricted.
Summary / Key Takeaways
- Memory Layout in C programming divides a program's memory into logical regions.
- The text segment stores executable instructions.
- The data segment stores initialized global and static variables.
- The BSS segment stores zero-initialized or uninitialized global and static variables.
- The heap is used for dynamic memory allocation and must be managed manually.
- The stack stores local variables, function parameters, and call information.
- Use the stack for short-lived data and the heap for dynamically sized or long-lived data.
- Proper memory management helps prevent crashes, leaks, and undefined behavior.
FAQ About Memory Layout in C
1. What is memory layout in C programming?
Memory layout is the organization of a program's memory into regions such as the text segment, data segment, BSS segment, heap, and stack, each serving a specific purpose during execution.
2. What is the difference between the stack and the heap?
The stack is automatically managed and stores local variables and function call information. The heap is manually managed using functions like malloc() and free() and is used for dynamic memory allocation.
3. Why are uninitialized global variables stored in the BSS segment?
The BSS segment allows executables to remain smaller because zero-initialized data does not need to be stored explicitly in the executable file. The operating system initializes it to zero when the program starts.
4. Why should I call free() after using malloc()?
Calling free() releases heap memory back to the system, preventing memory leaks and allowing the memory to be reused.
5. Why do memory addresses change every time I run a program?
Modern operating systems use Address Space Layout Randomization (ASLR), which places memory regions at different addresses each time a program starts to improve security against certain types of attacks.