G27
Self-checkedTier 1 12 min readpds.c-memory-layout

C memory layout: text, data, heap, stack

The six regions of a running C program's address space, which kind of variable lives in which region, and why that decides lifetime, initial value and whether you may write to it.

Why it exists

A running program is not just "variables in memory". The operating system gives it one address space, and the compiler and linker sort every object in the program into regions with different rules (the loader then maps them into memory). Where something lives decides how long it exists, what value it starts with, whether you can change it, and how fast it can grow. Questions of the form "which of these is stored where", and many pointer-bug questions, come from this one picture.

Prerequisites

Precise definitions

A C program's address space has six regions that matter. From the lowest addresses of the program image upward:

RegionWhat lives thereLifetimeInitial valueWritable
TextThe machine code of every functionwhole programthe compiled instructionsno (read-only, executable)
Read-only dataString literals, const globalswhole programthe stored constantsno
DataGlobals and statics with a non-zero initialiserwhole programstored in the executable fileyes
BSSGlobals and statics without an initialiser (gcc also puts ones initialised to zero here)whole programzero, filled in by the loaderyes
HeapMemory from malloc / calloc / reallocuntil freeuninitialised (calloc gives zeros)yes
StackLocal variables, parameters, return addresses (conceptually; a compiler may keep some in registers)until the function returnsuninitialised (indeterminate)yes

This table describes the usual gcc and Linux layout. The C standard itself defines only storage durations and says that modifying a string literal or a const-defined object is undefined; it does not say which memory region holds them.

Definitions to know exactly:

  • Storage duration (C11 6.2.4). Static: exists for the whole run (globals, static locals, string literals). Automatic: exists from entering the block to leaving it (ordinary locals and parameters). Allocated: exists from malloc (or calloc, realloc) to free. C11 also has thread storage duration (_Thread_local), which is not needed for GATE.
  • Stack frame. The block of the stack belonging to one function call (see recursion and the call stack).
  • Text, data and BSS are the three columns printed by the size command. Its text column covers the code and the read-only data; data is initialised writable data; bss is zero-initialised data. Everything except BSS takes space in the executable file; BSS is recorded as a size only. (The file actually has more than three sections, such as .rodata; size just groups them.)
  • Initialisation rule. Objects with static storage duration that you do not initialise are set to zero. Automatic objects you do not initialise have an indeterminate value; reading one is a bug.

Growth directions. On the usual x86 layout the stack grows toward lower addresses (each new call's frame sits below the caller's) and the classic heap (the one extended with brk) grows toward higher addresses; large malloc requests may instead be served from separate mappings elsewhere. This is a property of the platform, not of C. GATE questions that mention a direction state it.

How it works

Step through a real program. Each step places one object: you see its declaration, the region it landed in, and its real address. The addresses come from a run with address randomisation turned off so they are stable. With randomisation on, the absolute numbers change on every run, but the usual order of the regions and which object goes where do not (the gaps between regions do change).

Process memory map

Real addresses from a run

C source

1#include <stdio.h>
2#include <stdlib.h>
3
4const int limit = 100;
5int total = 10;
6int count;
7static int hits;
8
9int main(void) {
10 static int calls = 3;
11 int x = 7;
12 int arr[4] = {1, 2, 3, 4};
13 char *name = "GATE";
14 int *p = malloc(4 * sizeof(int));
15 /* the program now prints the address of each object */
16 return 0;
17}

Address spacehigh addresses ↑

Stack0x7ffffffdd000 to 0x7ffffffff000
empty so far

⋮ shared libraries and other mappings ⋮

Heap0x555555559000 to 0x55555557a000
empty so far

the executable file's own regions

BSS0x555555558048 to 0x555555558058
empty so far
Data0x555555558030 to 0x555555558048
empty so far
Read-only data0x5555555555f9 to 0x555555558030
empty so far
Text (code)0x555555554000 to 0x5555555555f9
empty so far

Step 0

A running C program has one address space, split into regions. Where a variable or function lives decides how long it lasts, whether it can be changed and what its initial value is. Step through the program to see where each object really lands.

0 / 12
real execution: addresses printed by the program itself; regions from linker symbols and /proc/self/maps. gcc (Debian 14.2.0-19) 14.2.0, linux x64. gcc -O0 -o memmap memmap.c && setarch x86_64 -R ./memmap (ASLR off so the numbers are stable)
With ASLR on, absolute addresses change on every run, but the order of the regions and which object lands in which region does not.

What to take from it:

  • Only the pointer is on the stack. p is a local, so p itself is on the stack, but the four integers it points to were obtained from malloc and are on the heap. name is likewise a stack variable holding the address of characters that sit in read-only memory.
  • static int calls is not on the stack even though it is declared inside main. Its initial value 3 is stored in the file, so it is in the data region, next to total.
  • Uninitialised globals cost nothing in the file. count and hits have no initialiser, so they are in BSS. The size line at the end shows the real split for this program.
  • Code has addresses too. main is a function, and it has an address in the text region. A function name used in an expression is converted to a pointer to that code.

Here is a fact the animation cannot show, but a real run can: writing to a string literal.

The same program, run for real

Real runs on this machine

Writing into a string literal

#include <stdio.h>
int main(void) {
    char *s = "GATE";
    s[0] = 'g';
    printf("%s\n", s);
    return 0;
}

What the compiled program did

-O0killed by SIGSEGV
(no output)

gcc -O0

-O2killed by SIGSEGV
(no output)

gcc -O2

The GATE answer

Undefined behaviour. A string literal may be stored in read-only memory, and writing to it is not allowed. On typical systems the program is killed by SIGSEGV. A char array initialised from the literal is a separate writable copy. Here every setting happened to agree, which proves nothing.

Worked examples

EX 1Sort the variables into regions

Easy

For the program in the animation, the regions are:

ObjectRegionWhy
maintextmachine code
"GATE"read-only datastring literal
limitread-only dataconst global
totaldataglobal with value 10
callsdatastatic local with value 3
countBSSglobal, no initialiser
hitsBSSfile-static, no initialiser
*p (the malloc block)heapallocated
x, arr, pstackautomatic locals

These regions are exactly what the real program reported (the generator stops with an error if any object lands elsewhere).

EX 2A static local remembers; an automatic local does not

Medium
#include <stdio.h>
 
int next(void) {
    static int n = 0;
    int fresh = 0;
    n++;
    fresh++;
    return n * 10 + fresh;
}
 
int main(void) {
    int first = next();
    int second = next();
    int third = next();
    printf("%d %d %d\n", first, second, third);
    return 0;
}

Output:

11 21 31

Step 1. n is static, so it lives in the data region and keeps its value between calls. It becomes 1, then 2, then 3.

Step 2. fresh is automatic, so it is created at 0 on every call and ends each call at 1.

Step 3. The returned values are 1⋅10+11\cdot 10+1, 2⋅10+12\cdot 10+1 and 3⋅10+13\cdot 10+1.

Why the calls are in separate statements. If you wrote printf("%d %d %d", next(), next(), next()) the order in which the three calls run is unspecified, so the printed order could differ between compilers. The page on undefined behaviour shows this with real runs.

EX 3Default values of globals and locals

Medium
#include <stdio.h>
 
int g[3];
static int s;
 
int main(void) {
    printf("%d %d %d %d\n", g[0], g[1], g[2], s);
    return 0;
}

Output:

0 0 0 0

Why. Both objects have static storage duration and no initialiser, so they are in BSS and start as zero. An uninitialised local int x; has an indeterminate value; no output can be predicted for it, so a correct GATE question never asks.

EX 4A pointer to a literal against an array copy

GATE level
#include <stdio.h>
 
int main(void) {
    char *s = "GATE";
    char t[] = "GATE";
    t[0] = 'g';
    printf("%zu %zu\n", sizeof s, sizeof t);
    printf("%s %s\n", s, t);
    return 0;
}

Output:

8 5
GATE gATE

Step 1. s is a pointer variable. Its size is the pointer size, 8 bytes on this 64-bit machine (a GATE question will state the pointer size, commonly 4 or 8). The characters it points to are in read-only memory and must not be modified.

Step 2. t is an array of 5 char (four letters plus the terminating '\0'), initialised by copying the literal. The array is an ordinary local on the stack, so sizeof t is 5 and t[0] = 'g' is legal.

Step 3. Writing s[0] = 'g' instead would be undefined and normally crashes (see the real run above).

EX 5Returning the address of something

GATE level
#include <stdio.h>
#include <stdlib.h>
 
int *from_static(void) {
    static int v = 5;
    return &v;
}
 
int *from_heap(void) {
    int *p = malloc(sizeof *p);
    *p = 6;
    return p;
}
 
int main(void) {
    int *a = from_static();
    int *b = from_heap();
    printf("%d %d\n", *a, *b);
    free(b);
    return 0;
}

Output:

5 6

Both are safe because the pointed-to object outlives the function: the static lives in the data region for the whole run, and the heap block lives until free.

The broken version returns &x where x is an ordinary local. When the function returns, its stack frame is gone, so the returned address points to memory that will soon be reused (a dangling pointer). Using it is undefined behaviour.

Cheat box

Cheat box

  • Regions: text (code, read-only) · rodata (literals, const globals) · data (initialised globals/statics) · BSS (uninitialised globals/statics, zero-filled) · heap (malloc) · stack (locals, frames).
  • Static storage (globals, static, literals): lives for the whole run; if uninitialised it is zero. Automatic (locals): lives until the block ends; if uninitialised it is indeterminate.
  • BSS takes no room in the executable file. Data does.
  • Stack typically grows down, heap up. Frame is popped on return.
  • A pointer variable and the thing it points to can be in different regions.
  • char *s = "x" points to read-only memory. char s[] = "x" is a writable copy.
  • size prints text, data and bss sizes of the executable.

Traps and GATE patterns

(needs verification: whether GATE treats a const global as read-only data or just as "data". gcc places it in read-only data, as the animation shows, but the C standard does not mandate a region, so a question that simplifies to "initialised data" may expect data.)

Connections

The same six regions are what the operating system builds for every process; see the process address space. A compiler thinks of this as storage organisation, and each stack frame is an activation record.

PYQs

3 previous year questions from your book. I re-solved each one myself (1 by running or simulating, 2 by reasoning only); the book's key is shown next to my answer. All PYQs

GATE 2026 · Set 2 · MCQ · 1 markQ2 · page 39 of the book
In C runtime environment, which one of the
following is stored in heap?
  • (a)A static variable declared inside a function
  • (b)An array of integers declared inside a function
  • (c)A dynamically allocated array of integers created using malloc() function call
  • (d)Return address of a function
year not read yetQ14 · page 42 of the book
Match the following:

(P) static char var ;
(i)
Sequence
of
memory
locations
to
store address
(Q) m= malloc (10);
m = NULL;
(ii) A variable located
in data section
of memory
(R) char*ptr[10];
(iii)
Request
to
allocate a CPU
register to store
data
(S) register int var1;
(iv) A lost memory
which cannot be
freed
  • (a)P→ (ii), Q→ (iv), R→ (i), S→ (iii)
  • (b)P→ (ii), Q→ (i), R→ (iv), S→ (iii)
  • (c)P→ (ii), Q→ (iv), R→ (iii), S→ (i)
  • (d)P→ (iii), Q→ (iv), R→ (i), S→ (ii)
GATE 2021 · Set 2 · MSQQ12 · page 154 of the book
If the numerical value of a 2-byte unsigned integer on
a little-endian computer is 255 more than that on a
big-endian computer, which of the following choices
represent(s) the unsigned integer on a little-endian
computer?
  • (a)0x6665
  • (b)0x0001
  • (c)0x4243
  • (d)0x0100

Self-check

Question 1

Where is each of these stored: a global int a[1000000]; with no initialiser, and the same array declared inside main? Which one makes the executable file bigger?

Show answer

The global is in BSS (static storage, no initialiser); the one in main is on the stack. Neither makes the file large: BSS is only a size in the file, and a stack array exists only at run time. A global with initialisers (int a[1000000] = {1};) would be in the data region and would add about 4 MB to the file. On a typical gcc and Linux build the global array above gives a file of about 16 KB with 4,000,032 bytes of BSS.

Question 2

Why can a local int big[10000000]; crash main while the same array as a global works?

Show answer

The local array is placed on the stack, whose size is limited (typically 8 MB on Linux). 40 MB does not fit, so the program overflows the stack. The global lives in BSS, which is not limited by the stack size.

Question 3

What does this print?

#include <stdio.h>
 
void f(void) {
    static int s = 0;
    int a = 0;
    s++;
    a++;
    printf("%d %d\n", s, a);
}
 
int main(void) {
    f();
    f();
    return 0;
}
Show answer

s is static and survives; a is recreated each call.

1 1
2 1

Question 4

After char *s = "hello"; char t[] = "hello";, which of s[0] = 'j'; and t[0] = 'j'; is legal, and why?

Show answer

Only t[0] = 'j';. t is a writable stack array that holds a copy of the characters. s points at the string literal in read-only memory, so writing through it is undefined behaviour (usually a crash).

Revision card

One-screen revision

  • Six regions: text, rodata, data, BSS, heap, stack.
  • Static storage = whole run, default zero (or null). Automatic = block lifetime, indeterminate unless initialised. Heap = until free.
  • Initialised globals/statics → data. Uninitialised → BSS. Literals and const globals → read-only.
  • Locals, parameters, return addresses → stack (grows down typically). malloc → heap (grows up).
  • p the pointer (stack) is separate from *p (heap).
  • char *s = "x": read-only. char s[] = "x": writable copy.
  • Never return the address of an automatic variable.

References

  • ·Bryant and O'Hallaron, Computer Systems: A Programmer's Perspective, 3rd ed., Section 7.4 Relocatable Object Files (.text, .rodata, .data, .bss) and Section 9.8 Memory Mapping
  • ·Aho, Lam, Sethi, Ullman, Compilers: Principles, Techniques, and Tools, 2nd ed., Section 7.1 Storage Organization
  • ·ISO/IEC 9899:2011 (C11), Section 6.2.4 Storage durations of objects

Verification

Every address, region and size in the animation is printed by a real gcc-compiled program run with ASLR off; the generator fails if any object lands in a region other than the one stated here. All C programs on this page are compiled and run by npm run verify:c. The prose and GATE-pattern claims have NOT yet had a human textbook check (the independent second pass is described at the end of these notes). SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand (long C programs were not re-read line by line: they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose). No defect found. Status raised to self-checked; verified needs a textbook or human check.

last verified: never