SrcForge

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

FeatureStackHeap
AllocationAutomaticManual (malloc())
DeallocationAutomaticfree()
SpeedFasterSlower
SizeLimitedLarger (system dependent)
LifetimeFunction scopeUntil explicitly freed
Common UseLocal variablesDynamic 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

WrongCorrect
Forgetting free(ptr)Free dynamically allocated memory when no longer needed
Returning the address of a local variableReturn 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 pointerCheck the pointer before dereferencing
Assuming stack variables persist after a function returnsUse 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.

Prev: Advanced PointersBack to hub