skip to content

questions

15

When you print the memory map of a running process, which regions does it contain and what does each hold?

level: juniorimportance: must knowfreq 76%

answer

  1. not one undifferentiated pool
  2. each range carries permissions
  3. some shared, some per thread
  4. code and constants are read-only
  5. one stack plus thread-local block per thread

basics

~20 s

A process maps several regions with different rules: read-only code and constants, initialized static data, zero-filled static data, one growable heap, one stack per thread, and per-thread thread-local storage. Placement decides lifetime and who may write.

solid answer

~40 s

A process maps several ranges, each with its own permissions and its own rules. There is the **code** region, readable and executable but not writable; a **read-only constants** region holding literal text and constant tables; **initialized static data** for process-lifetime variables that start non-zero, and a **zero-filled static data** region for those that start at zero; the **heap**, one per process, grown on demand and reclaimed either explicitly or by a collector; **one stack per thread**; and a small **thread-local storage** block per thread. Two questions separate them all: how many copies exist (one per process, or one per thread), and may the program write there. Placement, not the pointer's type, decides whether a store faults and how long the bytes survive.

go deeper

for a junior

Be able to name the regions and say what each holds: code, read-only constants, static data, the heap, one stack per thread, thread-local storage.

for a middle

Explain what actually differs between them - permissions, how many copies exist, and the moment each region's extent is decided and released.

for a senior

Use the map when diagnosing. Know which region a symptom implicates before reaching for a tool, and which regions grow with load or with thread count.

for a principal

Judge which regions a design leans on - many per-thread reservations against one shared heap - and set the footprint envelope processes in your fleet must fit.

## A process is a set of mappings, not one pool When a program starts, the loader does not hand it a single undifferentiated block of memory. It builds a **memory map**: a list of address ranges, each backed by something and each carrying **permissions** - readable, writable, executable. Almost everything else in this subject follows from that map, because which range an address falls in decides how long its bytes live, who is allowed to change them, and what happens when the program tries. A conventional map for a running multi-threaded daemon holds these regions. - **Code** - the machine instructions of the program and of every library it loaded. Mapped readable and executable, not writable. One mapped copy serves every thread. - **Read-only constants** - literal text, jump tables, constant lookup tables: values fixed at build time that the program promises never to change. Mapped without write permission, which is why a store through a pointer into it traps. - **Initialized static data** - variables that live for the whole process and start at a non-zero value. Their starting bytes ship inside the program image. - **Zero-initialized static data** - the same process lifetime, but a starting value of zero. The image records only the size, and the loader supplies a zero-filled range. - **The heap** - one region per process. It grows on demand when the allocator asks the operating system for more address space, and blocks inside it are reclaimed explicitly or by a collector, depending on the ecosystem. - **Per-thread stacks** - one region per thread, each holding that thread's chain of active calls. Created with the thread, released when it exits. - **Thread-local storage** - a small block per thread holding variables declared as one-per-thread. It shares the thread's lifetime, but unlike the stack beside it, its contents outlive every individual call. ## The two questions that separate them | Region | How many | Program may write | Extent decided | Ends when | |---|---|---|---|---| | Code | one, shared | no | build time | process exits | | Read-only constants | one, shared | no | build time | process exits | | Initialized static data | one, shared | yes | build time | process exits | | Zero-filled static data | one, shared | yes | build time (size only) | process exits | | Heap | one, shared | yes | run time, grows | block released | | Stack | one per thread | yes | thread creation | frame returns, thread exits | | Thread-local storage | one per thread | yes | build and run time | thread exits | Read that table column by column and the whole subject is in it: **how many copies exist** and **may the program write** are the two axes that actually distinguish these regions from one another. ## Why the split exists 1. **Protection.** Marking instructions and constants non-writable turns a whole class of bugs - a stray pointer walking past the end of a buffer into code - into an immediate, localised fault instead of silent corruption discovered hours later. 2. **Sharing.** Because code and constants are never written, many processes running the same program can be given the same underlying copy. A region that is written cannot be shared that way. 3. **Cost.** Reserving a range is cheap; shipping bytes is not. A region whose contents are known to be all zeros needs only a size recorded, which is why a large zeroed static array costs nothing in the program file. ## What this buys you in an interview The next question is almost always *which region does this value land in, and why*, and the map is what you answer it with: - A value whose lifetime is the whole process and whose name is fixed at build time goes to static data. - A value that belongs to one call, in one thread, goes to that thread's stack. - A value whose size or lifetime is not settled until run time goes to the heap, which is why it costs a pointer hop and a reclamation obligation. - A value that must be one-per-thread rather than one-per-process goes to thread-local storage - a distinct region, not a flavour of either of the others. Notice how much the map explains with no further machinery: why running 800 threads instead of 8 multiplies part of the footprint and leaves the rest untouched (only the per-thread regions are duplicated); why two identical literals can legitimately share one address (a region nobody writes can be de-duplicated safely); why you cannot hand a value to another thread by leaving it on your own stack (that range belongs to your call chain). ## Where this stops The map is a list of address ranges, not a statement about physical memory. How those ranges are backed by hardware - page tables, what is resident, what has been paged out - is a separate operating-system subject, and so is any one platform's naming for its own runtime areas. Learn the regions and their rules first; the backing story sits underneath them and answers a different question.

  • Which of these regions already exist before the program's first instruction runs?
    Code, read-only constants, both static-data regions and the first thread's stack are mapped by the loader before entry. The heap starts empty or tiny and grows on demand, and every further stack and thread-local block appears only when its thread is created.
  • Where does a large constant lookup table end up, and can the program change it?
    In the read-only constants region, if it is declared constant and fully initialized at build time: the bytes ship inside the program image and the mapping carries no write permission, so a store through a pointer into it traps. A table that must be mutated has to be copied into a writable region first.
  • Does a value's declared type decide its region?
    No. The region follows storage duration and who can still reach the value: process-lifetime variables go to static data, per-call values to the calling thread's stack, values whose size or lifetime is unknown at build time to the heap. The same type can land in any of them.

A workshop is not one table: there is a wall of printed instructions nobody may edit, a shelf of fixed reference cards, a shared parts bin everyone draws from, and one small private bench per worker.

saying these in an interview costs you the question

  • Says a process has only two regions, a stack and a heap
  • Thinks all threads share a single stack
  • Believes literal constants live on the heap
  • Claims every mapped region is writable by the program
  • Thinks static data is allocated fresh on each call
open as a page

What does one frame on a thread's call stack hold, and why does returning from the call release it for free?

level: juniorimportance: must knowfreq 72%

basics

~20 s

A call frame holds that call's return address, saved registers, local variables and spilled temporaries, plus space for outgoing arguments. Returning moves the stack pointer back past the whole frame, so release is one instruction with no bookkeeping.

open as a page

Why does storing through a pointer to a literal string constant fault, while storing into a heap block succeeds?

level: middleimportance: must knowfreq 58%

basics

~20 s

A literal lives in the read-only constants region, mapped without write permission, so the hardware refuses the store. A heap block sits in a writable mapping, so the identical instruction succeeds. The region's permissions decide, not the pointer.

open as a page

How do you estimate how deep a recursive call chain can go before it exhausts a thread's stack?

level: middleimportance: must knowfreq 58%

basics

~20 s

Divide the thread's stack size by the average frame size: a 1 MiB stack with 80-byte frames allows roughly 13,000 nested calls. Frame size is the lever - adding a 256-byte local buffer per frame cuts that to about 3,100.

open as a page

Before a compiler may place a new object on the stack instead of the heap, what must it prove?

level: middleimportance: must knowfreq 68%

basics

~20 s

That no reference to the object can be followed once the creating frame returns: it is never stored in longer-lived memory, returned, or published to another thread. Without that proof the object must go on the heap.

open as a page

What forces a short-lived local value onto the heap when its scope is a single function call?

level: middleimportance: must knowfreq 58%

basics

~20 s

Three independent causes. A reference escapes by being stored, returned or published; the size is not known when the frame layout is decided; or a closure captures the value and outlives the scope. Any one of them is enough.

open as a page

When a daemon's thread count rises from 8 to 800, which regions of its memory map multiply and which do not?

level: middleimportance: should knowfreq 48%

basics

~20 s

Only the per-thread regions multiply: each thread gets its own stack reservation and its own thread-local block. Code, constants, static data and the heap stay single and shared, so thread count scales just one term of the footprint.

open as a page

Why can a function that recurses only a dozen levels deep still overflow a thread's stack?

level: middleimportance: should knowfreq 45%

basics

~20 s

Because the limit is frame size times depth, and a single large local dominates the product. A 64 KiB buffer per call fills a 1 MiB stack in 16 levels, so a twelve-deep recursion is already using 768 KiB.

open as a page

Why does a value written into thread-local storage still sit there when a pooled worker thread picks up a later task?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Thread-local storage is a per-thread region whose lifetime is the thread's, not the task's. A pool reuses its threads, so a slot written during one task is still there for the next task unless something clears it.

open as a page

A recursive directory-tree walker overflows the stack on one customer's deeply nested tree - how do you tell unbounded recursion from legitimately deep recursion, and what do you change?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Read the failing stack. A short cycle of frames repeated to the limit, with arguments that never progress, means the recursion has no reachable base case - fix the termination or the cycle in the data. Many distinct frames tracking real input depth means the input is genuinely deep, so move the pending work into a heap-held work list and bound the depth explicitly.

open as a page

Why does a hot loop that built one small coordinate value per iteration without allocating start allocating heavily once each value is passed to a caller-supplied callback?

level: seniorimportance: should knowfreq 46%

basics

~20 s

The value now escapes. A caller-supplied callback is a target the compiler cannot examine, so it must assume the reference is retained. The proof that kept the value in the frame is withdrawn and every iteration allocates a real block.

open as a page

Should a latency-critical component rely on the compiler proving its temporaries non-escaping, or be designed so those temporaries never exist?

level: principalimportance: should knowfreq 33%

basics

~20 s

Split the codebase. On paths with a hard latency budget, shape the interfaces so the temporary is never constructed, because the proof is an optimization with no source marker and an ordinary edit can withdraw it. Everywhere else, rely on it.

open as a page

Why does declaring a 100 MB zero-initialized static array not add 100 MB to the program image on disk?

level: middleimportance: nice to knowfreq 26%

basics

~20 s

Because zero needs no bytes to record. The image stores the region's size and its symbols, and the loader maps a zero-filled range at start-up. Only static data with non-zero initial contents ships its bytes inside the image.

open as a page

What makes running off the end of a thread's stack a fault rather than silent corruption of the memory next to it?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

A guard page: one or more pages with no access rights placed immediately past the stack's limit. The first access that lands on it traps, so the runtime reports an overflow instead of letting the write land in whatever is mapped beyond. Detection is per access, not per frame.

open as a page

What does it mean that a non-escaping value is scalar-replaced rather than allocated anywhere at all?

level: seniorimportance: nice to knowfreq 27%

basics

~20 s

The value is deleted as an object: each field becomes an ordinary local kept in a register or a stack slot. Nothing is allocated, nothing is released, and the value has no address and no identity.

open as a page