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 |
|---|---|---|---|---|
| Machine code |
| Not an object | Executable code |
|
| Read-only data | Static | Read-only here |
String literal |
| Read-only data | Static | Persistent characters |
|
|
| Static | Non-zero initializer |
|
|
| Static | Non-zero initializer |
|
|
| Static | Non-zero initializer |
|
|
| 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 data |
Literal characters |
| Read-only data |
|
| Data |
|
| Data |
|
| Data |
|
| BSS |
|
| Allocated block |
|
| Allocated block |
|
| Modelled stack |
Pointer object |
| Modelled stack |
|
| Modelled stack |
Pointer object |
| 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.

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.

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 | C does not require it | Toolchain-specific |
BSS contains garbage | Static objects start at zero | Zero-filled storage |
| 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 | 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.




