Memory Layout of a C Program: Stack, Heap, Data, BSS and Text Explained

Place globals, static objects, literals, local variables, pointers and allocated values correctly using one runnable C program and an illustrative ELF/Linux map.

KnowledgeGate Team

Exam prep & CS education

Updated 21 Aug 20266 min read

Knowing that local variables use the stack and malloc uses the heap is not enough to place globals, static objects, string literals, instructions or pointer variables. One compile-ready program, carrying a distinct value into every region, settles each of those placements against a conventional 64-bit ELF process image. Addresses and section placements are implementation details, not C guarantees. The Coding & DSA Courses for Placements category gives you the broader programming path around this topic.

Memory layout of a C program: start with the right model

A process address space contains the virtual addresses available to one running program. A conventional map includes executable text, read-only data, initialised data, BSS for zero-initialised static objects, allocator-managed heap storage, memory-mapped regions, and one stack per thread.

This is an implementation model. C defines objects, values, scope, linkage and storage duration, not ELF sections, growth directions or fixed addresses. Section names, growth directions and addresses come from a toolchain instead: the one assumed throughout is a typical 64-bit ELF/Linux build.

An address printed with %p is virtual, not a RAM-chip location. Hardware and the operating system translate it through pages, so two processes can use the same virtual address for different physical storage.

Text, read-only data, data and BSS

On this stated toolchain, the declarations conventionally map as follows.

Source item

Value

Typical ELF region

Storage duration

Reason

main instructions

Machine code

.text

Not an object

Executable code

static const int read_only

5

Read-only data

Static

Read-only here

String literal

"KG\0"

Read-only data

Static

Persistent characters

int initialised

7

.data

Static

Non-zero initializer

static int file_static

11

.data

Static

Non-zero initializer

static int local_static

13

.data

Static

Non-zero initializer

int zero_initialised

0

.bss

Static

Guaranteed zero

Only the .text row is not an object at all. It holds the machine code compiled from main, so it has no storage duration and no C-level lifetime to reason about, and the loader maps it read-only and executable, which is why an accidental write into that region faults instead of rewriting the running program.

These file-scope integers and block-scope local_static have static storage duration, lasting for the full execution. local_static has block scope, not automatic duration. Scope controls name visibility, duration controls object lifetime, and linkage controls access from other translation units.

Despite the label "uninitialised memory", C zero-initialises static-storage objects. Executables typically record a zero-filled range instead of every zero byte; the loader supplies it before main.

Worked C program: place every object and value

Save this as layout.c, then compile and run it with cc -std=c17 -Wall -Wextra -O0 layout.c -o layout. Values are fixed; addresses can differ across builds and runs.

#include <stdio.h>
#include <stdlib.h>

static const int read_only = 5;
int initialised = 7;
int zero_initialised;
static int file_static = 11;

int main(void) {
    int automatic = 21;
    static int local_static = 13;
    char stack_copy[] = "KG";
    const char *literal = "KG";
    int *heap = malloc(2 * sizeof *heap);

    if (heap == NULL) {
        return 1;
    }
    heap[0] = 34;
    heap[1] = 55;

    printf("read_only=%d at %p\n", read_only, (void *)&read_only);
    printf("initialised=%d at %p\n", initialised, (void *)&initialised);
    printf("zero_initialised=%d at %p\n", zero_initialised,
           (void *)&zero_initialised);
    printf("file_static=%d at %p\n", file_static, (void *)&file_static);
    printf("local_static=%d at %p\n", local_static, (void *)&local_static);
    printf("automatic=%d at %p\n", automatic, (void *)&automatic);
    printf("stack_copy=%s at %p\n", stack_copy, (void *)stack_copy);
    printf("literal=%s chars_at=%p pointer_at=%p\n", literal,
           (void *)literal, (void *)&literal);
    printf("heap_values=%d,%d block_at=%p pointer_at=%p\n",
           heap[0], heap[1], (void *)heap, (void *)&heap);

    free(heap);
    heap = NULL;
    return 0;
}

For one illustrative build where sizeof(int) = 4, a single run places the values like this. The addresses are examples, never promised results.

Object or value

Sample address

Conventional region

read_only = 5

0x00402004

Read-only data

Literal characters "KG\0"

0x00402008

Read-only data

initialised = 7

0x00404028

Data

file_static = 11

0x0040402C

Data

local_static = 13

0x00404030

Data

zero_initialised = 0

0x00404038

BSS

heap[0] = 34

0x000000001A2B62A0

Allocated block

heap[1] = 55

0x000000001A2B62A4

Allocated block

stack_copy = "KG"

0x00007FFFFFFFE195

Modelled stack

Pointer object heap

0x00007FFFFFFFE198

Modelled stack

automatic = 21

0x00007FFFFFFFE1A4

Modelled stack

Pointer object literal

0x00007FFFFFFFE1A8

Modelled stack

These are classifications of sample values, not a required ordering, and stack_copy, the pointer objects heap and literal, and automatic are placed using the unoptimised model. The allocated block sits far above the static regions because allocator storage begins past the program's own data, and the modelled stack sits far above that again.

Here an int is four bytes, so heap[1] starts four bytes after heap[0]: 0x1A2B62A4 - 0x1A2B62A0 = 4. Their sum is heap[0] + heap[1] = 34 + 55 = 89. &heap is the pointer object's address; heap stores the allocated block's address.

stack_copy owns three array bytes containing "KG\0"; literal instead stores an address that points to separate literal characters.

Vertical 64-bit ELF memory map for the worked C program, labelling text, read-only data, data, BSS, heap and stack with sample addresses.

Stack and heap: location, lifetime and ownership

At main entry, automatic, stack_copy, literal and heap are automatic objects, represented in this model by the stack frame. The modelled-stack pointer literal targets characters with static duration. Likewise, stack pointer heap targets two int objects in allocator-managed storage.

With sizeof *heap = 4, malloc(2 * sizeof *heap) requests 2 x 4 = 8 bytes. At sample address 0x1A2B62A0, two four-byte elements receive 34 and 55. free(heap) ends their lifetime, making the old non-null value dangling. Assigning NULL prevents reuse through this pointer but does not repair copied aliases.

Automatic objects expire when their block exits, static objects at program termination, and allocated objects when released. Name visibility does not change these lifetimes.

Before-and-after free(heap): the heap block holding 34 and 55, then crossed out with the pointer set to NULL and its old address dangling.

From executable file to running process

The compiler emits code and data, the linker builds an executable, and the loader maps file-backed content plus zero-filled memory into a process. It prepares the initial thread stack and required shared libraries before main.

A real process map may also contain shared libraries, allocator arenas, mapped files, guard pages, thread-local storage and a stack per thread. Large malloc requests may use memory mappings rather than one growing heap.

Memory management in OS: paging, TLB, worked example connects these regions to OS virtual-memory mapping. Memory Hierarchy and Virtual Memory: Paging and TLB separates address-space layout from caches and physical memory. C sections and OS paging are related, not identical, abstractions.

Memory-layout assumptions that break on real builds

Reruns can change addresses. Address-space layout randomisation moves regions, position-independent executables relocate code and static data, and allocator history affects heap results. Values and storage-duration rules remain unchanged.

Optimisation can keep automatic = 21 in a register, fold read_only = 5 into instructions, merge literals, remove unused objects or inline functions. Printing addresses makes objects observable, not their section placement a C guarantee.

Growth arrows are not universal. Implementations choose layouts, multithreaded programs have multiple stacks, and mapped regions may separate heap and stacks. A map of this kind is typical for one model, never a claim that C always stores an object there.

C memory-layout traps and assessment patterns

Mistake

What goes wrong

Correction

All const is read-only

C does not require it

Toolchain-specific

BSS contains garbage

Static objects start at zero

Zero-filled storage

malloc pointer lives on heap

Pointer and target are confused

Classify separately

Every local is on stack

Optimisation may use registers

Placement is conventional

Leak means stack overflow

Failure modes are mixed

Leak means unreleased allocation

Dereference after free

Object lifetime has ended

Never access it

Address order is portable

Layout may change

Infer no C rule

Common prompts ask you to place declarations, separate scope from duration, distinguish heap from &heap, explain BSS's file footprint, identify lifetime end, or state a process map's assumptions.

Rapid checks: static int count; starts at 0 and conventionally maps to BSS. int *p = malloc(3 * sizeof *p); makes p automatic and three int objects allocated. After free(p), *p is invalid even if the pointer value appears unchanged.

Memory layout of a C program: the short version and next step

Classify each object by its lifetime, target and implementation model. Here, 5 and "KG" use read-only data, 7, 11 and 13 use data, zero-initialised 0 uses BSS, automatic 21 uses the modelled stack, and allocated 34, 55 use the heap block.

For concepts, MCQs and practice, use the C Language Course: Concepts, MCQs & Coding, or the focused C Programming Course. Compile the example, record your addresses, then explain differences from the illustrative map.