Before this stage, read Reversing’s Compiled and interpreted before Enough C and assembly. Those lessons distinguish source code, machine instructions and conceptual tool views. Here you apply that distinction to storage: a drawing can teach a boundary without being a captured memory layout.
A memory address identifies a location. A buffer is storage with a capacity, the number of positions it can hold. An index selects a position relative to its beginning; this lesson starts indices at zero. A stack frame is the storage and bookkeeping associated with a function call. The model places a local buffer there while that call is active.
Before you begin
- Recommended first
- Enough C and assembly to follow a compare and a jump, A beginner license check, Where practice is allowed
- Estimated time
- 18 min reading, 16 min activity. Setup and optional practice may take longer.
- Where you work
- Use the printed memory and source models in a desktop or mobile browser, with paper or a text editor. No compilation, debugger, pwntools installation, mitigation change or executable is needed. The layouts are conceptual, not measured process memory. Optional later tools need a separately checked owned environment and budget.
- Without a lab
- All evidence needed for the activity appears on this page. Copy it into notes if downloads are unavailable. Read the labeled model and predict each result; a paper trace practises the reasoning while tool operation waits for a lab. Reading needs no JavaScript. Answer checking needs JavaScript and a secure context; saving progress also needs usable browser storage.
After this lesson you can:
- Mark indices 0 through 5 on the six-position note() model.
- Mark indices 0 through 4 on the five-position tag() card.
- Say that tag()'s row is that call's storage only while the call is active.
01Example
Worked conceptual note() layout. The later guided tag() has a different capacity.
Buffer slot: 6 byte positions
Valid indices: 0,1,2,3,4,5
Last valid index: 5
Neighbor count: drawn for a visible boundary, not a compiler prediction
Lifetime in this model: while note() is active
Labels model:1000 etc.: row names, not observed addresses02Where a value lives
Capacity counts positions, while an index names one position. With zero-based indexing the highest valid index is capacity minus one. A count of six therefore ends at index five; index six would already be outside this buffer.
Lifetime describes when an object exists and can be used. In this model the local array belongs to the active call; returning ends that lifetime. A stored address does not keep its local object alive. Real compilers may keep some values in processor registers, reorder storage or remove it entirely, so the drawing is not a machine layout.
Other storage has different lifetimes: dynamically allocated storage, often called heap storage, must be released according to its allocation rules; a static object can last for the whole program. This page models only a local buffer, without predicting its actual address or neighboring values.
Count a buffer’s valid positions and explain when a local object’s storage can be used.Model. A teaching model drawn by hand. It shows the idea, not real tool output.
Read the full explanation below.
note() buffer · 6 positions · active
——————Needed bytes, including the terminator
0 positions needed; 6 available.
Six modeled positions have valid indices 0–5; 5 is the last valid index.
Follow the explanation
- Six modeled positions have valid indices 0–5; 5 is the last valid index.
- After note() returns, this local storage is unavailable for later use.
Read every state and value
1. Six modeled positions have valid indices 0–5; 5 is the last valid index.
- Object
- note() buffer
- Sample
- Not established
- Bytes shown
- None
- Needed
- 0
- Fit
- pending
- Lifetime
- active
- Selected Index
- 5
2. After note() returns, this local storage is unavailable for later use.
- Object
- note() buffer
- Sample
- Not established
- Bytes shown
- None
- Needed
- 0
- Fit
- pending
- Lifetime
- returned
- Selected Index
- Not established
03A picture with an edge
The drawing and address table both describe slot’s six positions and a modeled neighboring integer count. The text table supplies the complete interpretation if the figure is unavailable.
Read the rows as a diagram, not a stack-growth direction or a promise that count will follow slot in compiled memory. The labels prefixed model: are intentionally invented. No matched binary/debugger map establishes this layout. The next lesson compares capacity with requested written bytes.
Label: conceptual model for note(). Not a captured map, disassembly, or debugger session.
No binary was built. No address was observed. The hex-shaped labels are row names.
Call: note()
Buffer: slot, 6 positions, indexed from 0.
Highest valid index on this card: 5.
Neighbor in the model: count. It is the next row so the picture has an edge. It is not a saved return from a compiler.
model label | row | what it holds in this picture | lifetime in this model
model:1000 | slot index 0 | one position | while note() is the active call
model:1001 | slot index 1 | one position | while note() is the active call
model:1002 | slot index 2 | one position | while note() is the active call
model:1003 | slot index 3 | one position | while note() is the active call
model:1004 | slot index 4 | one position | while note() is the active call
model:1005 | slot index 5 | one position | while note() is the active call
model:1006 | count | neighboring integer | while note() is the active call
After note() returns, this model says these rows are no longer that call's storage.
A compiler may keep a value in a register instead. This table does not show a register.
04Trace tag
The guided call tag() has five positions. Apply the rule without importing the six-position example’s result.
Label: activity card for tag(). Same kind of model as note(). Different size.
Not a captured map.
Call: tag()
Buffer: tag, 5 positions, indexed from 0.
Count the last index from the zero-based rule, then compare it with the table and check below.
model label | row
model:2000 | index 0
model:2001 | index 1
model:2002 | index 2
model:2003 | index 3
model:2004 | index 4
Lifetime sentence on this card: the row belongs to tag() only while that call is active.
When the call returns, the model does not keep the row as tag()'s storage.
Check you can make from this card, before the question:
- five positions
- first index 0
- last index 4
- the row ends as this call's storage when tag() returns
- Copy capacity five and starting index zero from tag-activity.txt.
- List its five indices as 0,1,2,3,4 and identify four as the last valid index.
- Add its lifetime: these local positions belong to tag() while that call is active; returning ends that modeled object lifetime.
- Stop with count, last index and lifetime. Do not invent addresses or compile source to complete this trace.
05Optional challenge
For about ten optional minutes, imagine a function returning a pointer to its local array. A pointer is a value holding an address. Explain why retaining that address after return does not extend the array’s lifetime, even if the old bytes have not visibly changed. This is a source-reading question, not a program to run.
06Fresh cases for the checks
Use these fictional cases for the checks below. They have different inputs from the worked examples. Read them here or make notes; no installation or external service is needed.
Memory case A: conceptual capacityOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.
Function label() has a local byte array with 7 positions, indexed from 0.
Each position stores one byte. No write is requested.
The local array belongs to this call while label() is active.
The diagram label model:2400 is a row name, not an observed address.
Memory case B: independent small arrayOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.
Function tag_new() has 4 byte positions indexed from 0.
Rows to classify: position 0; position 3; position 4.
This is a conceptual layout, not compiler output.
07Explain your evidence
Optional written self-check: explain one decision using the supplied case. No tool run is required.
Check your explanation: count zero-based indices; separate capacity from final index; connect storage lifetime to the active call; identify conceptual row labels and missing machine-layout evidence.
08Common mistakes
If the last index equals capacity, list every position starting at zero and count them. The count and final label have different jobs.
If a drawing’s neighbor becomes an actual compiler layout, restore its conceptual label; a real build and matched inspection would be needed.
If an address seems to keep a local object alive, compare the object’s lifetime with the time the pointer is used.
References
- CERT C: ARR30-C, out-of-bounds pointers and subscripts
Array bounds, out-of-range subscripts and pointer operations; invalid accesses can cause undefined behavior.