Skip to content

C Memory Layout — Stack, Heap, Data Segment, and Text Segment

DodaTech Updated 2026-06-28 8 min read

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

  1. Initialized data: global and static variables with initial values
  2. BSS (Block Started by Symbol): global and static variables without explicit initializers (zeroed at program start)
  3. 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:

  1. Return address: where to resume execution after the function returns
  2. Saved frame pointer: previous function's base pointer
  3. Local variables: space for the function's local variables
  4. 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

  1. What is stored in the text segment? The program's machine code instructions. It is read-only and executable.

  2. What is the difference between stack and heap allocation? Stack: fast, automatic, limited size. Heap: slower, manual, large capacity.

  3. Where are global variables stored? In the data segment (initialized) or BSS (uninitialized).

  4. What causes a stack overflow? Excessive function calls (deep recursion) or large local arrays that exceed the stack size limit.

  5. 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 the default stack size on Linux?

Typically 8 MB. Check with 'ulimit -s'. You can increase it with 'ulimit -s unlimited' or setrlimit().

Can the heap grow into the stack?

Potentially, on systems without ASLR. Modern systems randomize addresses to prevent collisions.

Where are string literals stored?

In the text segment (read-only). Modifying a string literal causes undefined behavior (often a crash).

What is BSS?

Block Started by Symbol. It holds uninitialized global and static variables, which are zeroed at program startup.

How does the OS know which segment something is in?

The program header in the executable (ELF on Linux, PE on Windows) defines segment boundaries. The OS loader maps them into memory.

What is Next

Now that you understand memory layout, proceed to Structs to learn about defining custom data types that group related variables.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse C