Address dependency 2026-10-31
A value loaded by one instruction directly contributes, through register computation, to a later memory access's memory address. Loading a pointer and then reading fields through that saved pointer is a natural example. This differs from a control dependency.
Alias analysis 2026-10-31
Analysis of whether two accesses may designate the same storage location. Common subexpression elimination must not reuse a previously loaded value after a store that might change that location, unless an additional argument justifies it. A saved memory address can remain valid even though a value previously loaded at that address is invalidated.
Computer memory Created 2026-10-13 Updated 2026-11-05
Computer memory stores information that a computer can retrieve while executing computer programs. A memory location holds a value, and a memory address selects a location under the machine's addressing rules. In a byte-addressed example, consecutive bytes have consecutive memory addresses; a four-byte value can occupy four locations. The interpretation of those stored bits as a number, an instruction or another representation belongs to the computer program and architecture.
Computer memory has different physical and logical roles. Non-volatile memory retains stored information without power, while other technologies require power to retain it. A memory cache holds selected information for faster reuse; virtual memory presents an operating-system process with its own address space. These describe different properties, so a cached value and its virtual address should not be confused with the physical storage holding it.
In the paging examples, resident memory pages occupy main computer memory, and address translation maps a virtual address to a physical address. The operating system can change a mapping while keeping an operating-system process's numerical virtual address unchanged. This separation makes controlled sharing and isolation possible.[ref]
Operating Systems: Three Easy Pieces by Remzi H. Arpaci-Dusseau and others. chapter 2, memory virtualization overview
Framebuffer 2026-11-05
A framebuffer is computer memory holding an image's pixel values in a representation usable by graphics hardware or software. A pixel format specifies how stored bits encode those values. The image's dimensions alone do not determine its complete memory layout: rows can include padding, and different layouts can organize components differently.[ref]
The Frame Buffer Device API in the Linux Kernel documentation. §2, Types and visuals; related Frame Buffer Device introduction, §§0–1
For a concrete packed-row model, let the image be three pixels wide and two rows high, using four bytes per pixel. A row's actual pixel data occupies bytes. If each row starts 16 bytes after the previous one, the framebuffer stride is 16 bytes and four padding bytes separate consecutive pixel rows. Reserving two complete row slots takes bytes, although the pixel values themselves occupy only 24 bytes.
With zero-based coordinates, base memory address , stride and bytes per pixel, the start of a pixel in this stipulated layout isThus starts at . Replacing by the packed width12 would instead address the wrong location. This formula assumes packed, byte-addressable rows; it is not a formula for every possible pixel format.
The Linux interface makes the distinction explicit:
line_length reports row length in bytes, separately from resolution and bits_per_pixel. A program should use the layout supplied by the interface when interpreting a framebuffer, rather than infer it from the visible dimensions alone.[ref]The Frame Buffer Device API in the Linux Kernel documentation. §3, Screen information, fixed line_length and variable resolution/depth fields
Framebuffer stride 2026-11-05
A framebuffer stride is the memory address increment between the starts of consecutive image rows in a specified framebuffer layout. For a row-based packed pixel format, it is commonly measured in bytes. It includes any padding between rows, so the image width and pixel format alone do not determine it. The Linux frame-buffer interface reports this byte count as
fb_fix_screeninfo.line_length.[ref]The Frame Buffer Device API in the Linux Kernel documentation. Section2, FB_TYPE_PACKED_PIXELS and end-of-line padding; section3, line_length
Assume each pixel occupies complete bytes, rows advance by a positive stride , and coordinates are zero-based. With base address ,This formula describes the stated packed-row layout. Formats with a different organization need their own addressing rule.
For an original example, choose an image width , height and per pixel. The pixel data in each row occupies 15 bytes. If the chosen allocator aligns each row to a multiple of eight bytes, it usesThere is one padding byte per row slot. The last pixel, at , starts at and occupies offsets 44, 45 and 46. Three complete row slots occupy 48 bytes, whereas the span through the last pixel ends at exclusive offset47:The allocation rule and the bytes actually containing pixels are distinct parts of the layout.
A cropped view need not become tightly packed. Starting a three-pixel-wide crop at original coordinate gives new base . Its next row still begins at , although each cropped row contains only nine pixel bytes. Substituting nine for the stride would instead start that row at .
Query the actual layout supplied by the interface before traversing memory. In the documented Linux interface, a successful format change can also update
line_length, so the stride belongs to the selected layout rather than being an immutable consequence of visible width.[ref]The Frame Buffer Device API in the Linux Kernel documentation. Section4, format configuration and updated type, visual and line_length fields
Memory address 2026-10-24
A numerical identifier for a location in computer memory. In a byte-addressed system, consecutive four-byte array elements have memory addresses differing by four.
Memory alignment 2026-11-05
Memory alignment restricts the memory addresses at which an object may begin. An alignment of bytes means that its starting byte address satisfies . For example, address satisfies eight-byte memory alignment, whereas address does not. Alignment is a constraint on location, rather than another name for an object's size.
In the C programming language, object types have alignment requirements;
_Alignof queries a type's requirement and _Alignas can request a supported stronger requirement. The actual values depend on the implementation. The C11 draft distinguishes fundamental from implementation-supported extended alignments and requires valid alignment values to be powers of two.[ref]An application binary interface makes such choices concrete. Under the ordinary AAPCS64 layout, a C structure containing a
char tag followed by a double value has the character at offset , seven unused padding bytes, and the double at offset . Its total size is bytes and its alignment is bytes. The application binary interface aligns an aggregate as strictly as its most strictly aligned member and rounds the aggregate size up to a multiple of that alignment; this also keeps each element properly aligned in an array of these structures.[ref]This example is an explicit platform convention, rather than a universal promise that every C
double needs eight-byte memory alignment. Likewise, a processor's ability to execute an unaligned load does not by itself relax the C programming language's object-access requirements. Keeping the language rule, the application binary interface layout, and the machine instruction separate explains why a byte buffer cannot automatically be treated as a properly located object of any chosen type. Memory allocation 2026-11-05
Memory allocation reserves computer memory for a program’s data and makes that storage accessible through memory addresses. Allocation determines where data can live and how long the storage remains available; initialization determines the data’s initial value. A language can arrange storage automatically for local variables, retain storage for the duration of a program, or request storage dynamically while the program runs.
In the C programming language,
malloc(n) requests a block of bytes and returns a pointer to it, or a null pointer if allocation fails. Successful allocation does not initialize those bytes. The free operation releases a previously allocated block for subsequent allocation; accessing it through an old pointer is no longer valid.[ref][ref] Allocation and deallocation are therefore separate from reading and writing an object. Deallocation need not erase its former contents or immediately return its storage to the operating system.For a process using virtual memory, a contiguous interval of memory addresses does not require a contiguous interval of physical memory. The memory management unit can map successive virtual pages to different physical page frames. Conversely, a successful small allocation may be satisfied from an existing free block without asking the operating system for another page.
Two small bookkeeping examples explain why free capacity alone does not determine whether an allocation succeeds. If an allocator rounds a 17-byte request up to a 32-byte allocation unit, 15 bytes are unused inside that unit: internal fragmentation, before counting any allocator metadata. If two free 32-byte blocks are separated by an occupied block, a request for one contiguous 48-byte block cannot fit in either hole, even though 64 bytes are free. That is external memory fragmentation, under the assumption that the occupied block cannot be moved and no further storage is available. Adjacent free blocks can sometimes be combined; separated ones require a different placement or movement strategy.
Past exam of the computer science course of the University of Oxford 2015 Compilers 3 a Solution Created 2026-10-31 Updated 2026-11-05
Use the following explicit tree notation for the intermediate representation.
C(k) is a constant, R(r) a processor register, M(e) a word load from memory address e, ADD addition, and SHL(e,2) a left shift by two bits. MOVE(destination,source) assigns either a processor register or the computer memory word designated by M. Parentheses show complete tree structure, so these displays specify trees rather than already shared directed acyclic graphs.Let be the constant base memory address of the global array
a, and the constant offset of local i from the frame pointer fp. Each integer occupies four bytes, and array indices are zero-based; a fixed nonzero lower bound can instead be absorbed into . Local t is represented by R(t). After replacing multiplication by four with a left shift, the three trees are:MOVE(R(t),
M(ADD(C(A),
SHL(M(ADD(C(A),
SHL(M(ADD(R(fp),C(I))),2))),2))))
MOVE(M(ADD(C(A),
SHL(M(ADD(C(A),
SHL(M(ADD(R(fp),C(I))),2))),2))),
M(ADD(C(A),SHL(M(ADD(R(fp),C(I))),2))))
MOVE(M(ADD(C(A),SHL(M(ADD(R(fp),C(I))),2))),
R(t))Every
M(ADD(R(fp),C(I))) loads i; the next enclosing M loads a[i]; the outermost M in the first tree loads a[a[i]]. Each array index is converted to a byte offset by a left shift of two bits. The address rule used in all three trees is The source language's array bounds checks are omitted as requested. Past exam of the computer science course of the University of Oxford 2015 Compilers 3 b Solution 2026-10-31
The useful repeated program expressions, nested inside one another, are the memory address of
a[i], its value, and the memory address of a[a[i]]. Materialize each maximal reusable tree at the level at which it is shared, using fresh temporary variables u, v, and w:MOVE(R(u), ADD(C(A),SHL(M(ADD(R(fp),C(I))),2)))
MOVE(R(v), M(R(u)))
MOVE(R(w), ADD(C(A),SHL(R(v),2)))
MOVE(R(t), M(R(w)))
MOVE(M(R(w)), R(v))
MOVE(M(R(u)), R(t))Thus
u is a saved memory address, v the old integer value of a[i], w the second saved memory address, and t the old value at that second location. The two large address trees occur both in loads and as store destinations. After extracting them, the repeated load of a[i] inside the second address and as the middle assignment's source becomes v. Extracting every smaller nested tree again would add no benefit.The relevant alias analysis facts are that local
i does not occupy any location written through global a, that frame pointer fp and base stay fixed, and that t,u,v,w are distinct processor registers or otherwise nonoverlapping temporary variables. There are no intervening calls, volatile memory accesses, or concurrent changes. Under these assumptions, loading i once remains valid even across the array stores. Before the middle store, no array location has yet changed, so both its target memory address and its source value can be computed and saved earlier.No assumption that the two array locations are distinct is required. The middle store may change
a[i] when its target coincides with it, but v is not loaded again afterwards: only the saved address u is needed for the final store. It would be unsound to retain an arbitrary array load across an aliasing store and then treat it as the new contents; that is not what these trees do.For a language with a true global array, a distinct local variable, and no arbitrary means of fabricating pointers to unrelated storage, these facts follow simply from declarations and the stack frame layout. Accesses must denote valid array elements. In an unrestricted low-level language with overlapping storage, arbitrary pointer arithmetic or unchecked out-of-range accesses, that conclusion requires extra guarantees. Omitting emitted array bounds checks does not itself prove that all memory addresses are valid. If denotes a word load, the saved quantities are
Past exam of the computer science course of the University of Oxford 2015 Compilers 3 e Solution 2026-10-31
The source rewrite already saves the old
a[i] in register-allocated t. Denote the original integer index by , its original element by , and the original element there by . The second fragment first sets t=j, then writes to a[i0], and finally writes to a[j]. Thus it performs the same intended swap without needing to recompute the second memory address from a possibly modified array element.For completeness, the first fragment is also correct when the two locations might alias. If , its middle store changes only
a[j], so the final a[i] still denotes the first location. If , then , and the supposed swap is a no-op in both fragments. There is no need to assume distinct locations.For the second fragment, common subexpression elimination of maximal addresses gives, using
z for the second address:MOVE(R(u), ADD(C(A),SHL(M(ADD(R(fp),C(I))),2)))
MOVE(R(t), M(R(u)))
MOVE(R(z), ADD(C(A),SHL(R(t),2)))
MOVE(M(R(u)), M(R(z)))
MOVE(M(R(z)), R(t))The nested memory-to-memory assignment requires an additional scratch processor register
rv on a reduced instruction set computer. A direct translation is therefore:MOV rb, #A
LD ri, [fp + I]
LSL ru, ri, #2
ADD ru, rb, ru
LD rt, [ru]
LSL rz, rt, #2
ADD rz, rb, rz
LD rv, [rz]
ST rv, [ru]
ST rt, [rz]The same compiler need not produce any better code: this has the same ten-instruction count as part (c). It still performs two element loads, two element stores, two shifts and two separately materialized address additions, plus the base and local-index setup. The processor register roles and store order differ, but the work is the same. The corresponding folding of register-offset addressing removes both
ADD instructions here too, yielding the same eight-instruction count as part (d).The key alias analysis advantage available in both fragments is that
i remains unchanged by the array stores. In the second fragment, t is also unchanged after its first assignment because it lives in a processor register. Its name alone does not produce a further saving beyond what the first fragment's common subexpression elimination already achieves. Exact instruction selection is not uniquely prescribed by the paper; these explicit translations demonstrate why the claimed source-level efficiency advantage does not follow:A store buffer holds the memory address and value of a store instruction while the memory cache or backing computer memory completes the write. The in-order pipeline can continue after enqueueing the write, rather than waiting for a possibly long memory access latency. A full store buffer eventually causes a pipeline stall; buffering hides finite delays, not an indefinitely inadequate service rate.
Without a memory management unit, the comparison can use the actual physical addresses; there is no address translation or synonym problem to solve. Suppose If the write is still in the store buffer, reading only computer memory could incorrectly return zero. The load instruction searches older buffered store instructions for an overlapping memory address. If the newest matching write covers the requested bytes and its data are ready, store-to-load forwarding returns seven directly. With several pending writes to
x initially contains zero and these operations execute in order:STORE [x], 7
LOAD r1, [x]x, the youngest older matching write wins. If the relevant data are not ready, the load instruction waits; partial overlaps require merging the covered bytes or conservatively waiting for the writes to drain.A load instruction from an unrelated address
y can bypass those buffered writes, subject to the architecture's memory model. Buffer writes to overlap them with useful execution, but preserve each load's dependence on older writes to the same bytes. Device accesses through memory-mapped input-output need their specified ordering and should not be treated as ordinary cacheable computer memory.Speculative execution allows younger instructions to proceed while some condition on an older instruction is unresolved. Here the prediction is that a store instruction will pass its memory management unit's protection and translation checks. Successful prediction avoids waiting; failure requires discarding younger work and taking the older instruction's synchronous processor exception. A result computed speculatively must not become an irreversible architectural change before it is safe.
In the displayed five-stage in-order pipeline, the store instruction's memory address is computed in EX and its memory operation is in MEM. Take the permission check to finish at the end of MEM, with a one-cycle cache hit, no data hazards and sufficient store buffer capacity. A concrete conservative implementation without speculative execution fetches no younger instruction until this check has passed. Compare the start of a stream of independent, nonfaulting store instructions:
The conservative version inserts three otherwise unnecessary cycles after each such store instruction, whereas the speculative version can start one instruction per cycle. For stores, counting through WB, the times are and cycles. ThusMore generally, with a fraction of such store instructions and no other delays, this particular interlock gives an asymptotic comparison . The maximum is four, not five: the permission check is resolved in MEM, so waiting for the empty store WB stage is unnecessary.
The baseline's interlock position matters. If it allows younger IF and ID work but prohibits younger EX until the end of the store's MEM stage, only one EX cycle is lost per store: successive stores reach EX in cycles , compared with under speculative execution, giving a limiting factor of two for that baseline. Holding at ID instead gives two lost cycles per store and a factor of three. The fourfold figure compares against a pipeline that stops younger fetch; an implementation that already permits younger front-end work has a smaller remaining benefit. The source specifies the five stages but does not prescribe this alternative's interlock position.[ref]
On a protection failure detected at the end of MEM, younger instructions occupy EX, ID and IF and can be invalidated before their normal WB. Younger store instructions must also be prevented from draining from a store buffer before validation. Earlier updates to processor condition flags, if any, need delayed commitment or restoration too. This is what makes the prediction compatible with a precise exception; merely continuing to execute is insufficient.
In IF, fetch
PREFETCH normally. In ID, recognize its opcode, read the base processor register and immediate offset, and set controls selecting a prefetch request rather than a load instruction or store instruction. Ordinary register forwarding and data hazard interlocks still apply to that base processor register. In EX, use the arithmetic logic unit to form the effective memory address.Latch that memory address and a prefetch-valid control into EX/MEM. In MEM, put the memory address and any hint information on the dedicated request computer bus, or enqueue them in a small prefetch-request queue. The cache prefetching engine handles lookup, allocation and the later cache line return independently. Do not wait for the fill or select the returned data as a register file input. In WB, disable architectural processor register and processor condition flag writes; the instruction simply leaves the in-order pipeline.
If the request path is busy, dropping the hint is compatible with the contract in part (ii), and preserves its nonblocking purpose. A design that instead stalls until queue space exists can still be correct but reduces the performance benefit. A memory management unit must not turn a speculative hint into an ordinary protection fault: discard an invalid target, or use an appropriately checked nonfaulting translation path. Suppress device transactions for memory-mapped input-output and avoid speculative writes. Use the existing address-generation datapath, add a prefetch request/control path, and disable demand-load waiting and writeback.
Define
PREFETCH [a] as a software prefetch hint: initiate a request for the cache line containing memory address a, then continue without waiting for that cache line to arrive. It produces no value in a destination processor register, changes no program data and creates no data-result dependence for later instructions. It may be ignored if resources are unavailable. A useful hint is nonfaulting for an inaccessible or untranslated target: a later real load instruction must still perform its ordinary access checks. This is a proposed contract for the instruction, rather than a guarantee that the patent abstract alone fixes every detail of its semantics.For example, after reading the next pointer of a linked list node, prefetch that next linked list node while processing the current one. Replacing the hint by an otherwise unused ordinary load instruction on this in-order pipeline would wait in MEM on a cache miss before the current computation could start. It would also consume a destination processor register and might raise a synchronous processor exception. A harmless hint can be scheduled earlier, even on a path where the data will never be demanded. The crucial semantic difference is that cache filling may continue after the prefetch instruction itself has finished.
A software prefetch is not a memory barrier and does not establish acquire-release synchronization or promise that later accesses will hit. Its purpose is performance; correctness must survive the hint being ignored.
A reorder buffer records instructions in program order, including those with no destination processor register. Each entry needs a completion state before instruction retirement can pass it. Thus a store instruction must report that its memory address and data are ready and whether execution detected a fault, even though it has no ordinary register file result.
Suppose an older division occupies the head of the reorder buffer, followed by a store instruction and an addition. The latter two can finish first. The store instruction's completion message identifies its reorder buffer entry, carries or identifies the buffered memory address and data, and supplies its status. The entry is then ready, but its write remains private until it is the oldest instruction allowed to commit. When the division retires successfully, the store instruction can commit to the store buffer or memory system, and the addition can subsequently retire. If the division faults, the younger store instruction is discarded without changing visible computer memory.
If the store instruction never reported completion, its reorder buffer entry would remain unready and block instruction retirement indefinitely. Conversely, treating it as completed at issue could commit missing data or suppress its later fault. The common data bus carries tagged completion information as well as register values. A false-predicated instruction also needs a completion indication even when it changes no data.
This answer uses the diagram's shared result/completion path. An architecture can instead provide a dedicated store queue completion path to the reorder buffer; there is no universal requirement to send store data over the same physical wires as arithmetic results. What is essential is delivery of the tagged completion and exception status before instruction retirement.
Separate the two required orders. A writer must make all initialization store instructions to a fresh version visible before another CPU core can use the publication of its address. A reader must load
p before the load instructions of fields addressed through the saved pointer. Assume ordinary coherent cacheable computer memory, atomic aligned pointer accesses, immutable published versions, and the lifetime guarantee from part (ii). The sequences below are architectural operations; a compiler must preserve the stated accesses and dependencies.TSO. Total store order preserves store-to-store order, so initialization writes cannot become visible after the later pointer publication. It also preserves load-to-load order, so reading the pointer precedes reading its fields. Its store-to-load relaxation, illustrated in part (b), does not break either required ordering. ThereforeNo drain of every reader's store buffer and no explicit cache flush is required. This statement is about hardware ordering; unsynchronized ordinary C or C++ accesses to
p would still need the language's atomic operation and acquire-release synchronization facilities to avoid a data race and unwanted compiler transformations.Power/ARM. These weaker memory models can expose a new pointer before all the writer's initialization store instructions are visible. Put a write-ordering memory barrier between the last initialization write and the store of The scope must include every participating CPU core. A broader full barrier is sufficient but can cost more. Without the writer barrier, observing the new
p. A sufficient sequence uses lwsync on POWER architecture or an appropriately scoped DMB, such as DMB ISH, on the ARMv7 ARM architecture family:writer:
STORE [new + field1], value1
STORE [new + field2], value2
publication_barrier
STORE [p], newp and stale initialization data is exactly the unwanted message-passing outcome.[ref]Understanding POWER Multiprocessors (2011). §§2.1–2.2, message passing and ordering by
sync/lwsync, printed pp. 2–4For the reader as actually specified, the saved pointer is an address dependency: the loaded value directly determines each subsequent field's memory address.Both POWER architecture and the relevant ARM memory models respect such dependent reads. Thus no additional reader hardware fence is needed when every data access retains this real address dependency. The writer's publication barrier together with the dependent reads supplies the necessary ordering. A mere test of
reader:
q := LOAD [p]
a := LOAD [q + field1_offset]
b := LOAD [q + field2_offset]p followed by unrelated fixed-address reads would be a control dependency, which is not sufficient here.[ref]Understanding POWER Multiprocessors (2011). §2.2, “Using dependencies” and “Control dependencies, observable speculative reads, and isync”, printed p. 3
If that address dependency is not retained, use an acquire operation or an explicit read-ordering memory barrier after loading
p and before reading fields. DMB ISH on ARMv7 or lwsync on POWER architecture is a conservative sufficient choice for this load-to-load ordering; a suitable conditional branch followed by isync is another POWER acquire implementation. On later ARM instruction sets an acquire load instruction can express the reader requirement directly. Such stronger forms are often convenient even though they are not minimal for the dependent sequence in this question.| Model | Before publishing p | After reading p |
|---|---|---|
| Total store order | No extra hardware fence | No extra hardware fence |
| Power/ARM, genuine dependent field reads | Write-ordering publication barrier | Address dependency suffices |
| Power/ARM, general reads without the dependency | Write-ordering publication barrier | Acquire or sufficient read-ordering barrier |
The minimal answer is a writer publication barrier on Power/ARM, with the stated reader's address dependence doing the read-side ordering; TSO needs neither extra hardware fence. None of these choices repairs an invalid pointer, premature memory reclamation, or a lost update between writers.
O1 supplies mutual exclusion over each complete operation. A reader sees the protected state from a serial order of readers and writers; a writer can safely mutate the existing object while holding the common mutex. If an update reads the old state and modifies it, the entire read-modify-write can be protected against a lost update.
O2 has different semantics. Assume an indivisible aligned access to
p, correct publication ordering, and no later mutation of a published version. A reader chooses one version when it loads p; the subsequent load instructions use the same saved pointer, so an overlapping writer need not block that reader. The reader may continue observing the old complete version after the writer has published the new one. A sufficiently specified snapshot read can still be linearizable, with the load of p as its linearization point: a read overlapping a write is allowed to come before that write in the abstract order. Thus reading an old version during overlap does not by itself prove failure of linearizability.Multiple writers can each construct a valid replacement and race to publish it. If the operation is simply “replace the whole object”, atomic pointer stores give a last-publication order. If each writer instead computes a change from the previously published state, O2 alone does not preserve both changes. Two writers can both read a counter value zero, independently build a version containing one, and publish their respective versions; the final counter is one. A writer mutex or compare-and-swap with retry is needed when the required operation must avoid that lost update. Memory barriers alone do not provide this exclusion.
Releasing the old object's computer memory immediately after publishing a replacement is unsafe. For example:The final operation is a use-after-free: it may crash, see allocator metadata, or read a different object that reuses the same memory address. An atomic update of
reader: q := LOAD [p] // gets the old object
writer: STORE [p], new_object
writer: free(old_object)
reader: LOAD [q + field] // accesses freed storagep does not update the reader's already copied pointer. Freeing the new object immediately would of course also invalidate p itself.Publication and reclamation are separate: publish a fully initialized version, and retain every old version until all readers that might hold it have finished. A read-copy-update grace period or a suitable hazard pointer protocol can provide that lifetime guarantee; naive reference-count acquisition after loading an unprotected pointer can itself race with freeing. Correct memory barriers in part (iii) ensure visibility, not object lifetime.
Use two zero-address instructions for indirect access to computer memory. Define The successive stack suffixes are , then , then , then empty. HenceThe surrounding stack is restored. Other
load to replace the top memory address by the value at that address, and define store to pop a value followed by the address just beneath it:Both have no encoded address operand; the stack supplies it. If the known fixed memory addresses of the variables are , implement the assignment withconst p_x
const p_y
load
storestore conventions are possible, but the operand order must be stated and used consistently.A computed memory address is just another integer on the operand stack. For a pointer held at fixed address ,
const p; load obtains the pointed-to address; another load obtains its contents. More generally, any expression that leaves an address on top may be followed by load, or may be retained beneath a computed value for store.For an array whose base address is and whose element size is address units, the address of entry is , where stores the index. Add zero-address instructions The first six instructions leave the element address . The remaining suffix evolves aswith the final effectThe address is computed once and duplicated, so both accesses refer to the same array element. For unit-sized elements omit
mul and dup, with dup mapping to . A suitable sequence isconst b
const p_i
load
const w
mul
add
dup
load
const 1
add
storeconst w; mul. If the base address is itself held in a variable, replace const b by the instructions loading that variable's value. No instruction other than const requires an explicit operand.