NASM • MEMORY & STACK

NASM Memory Addressing and the x86-64 Stack: A Practical Guide.

Understand how NASM programs locate data in memory, construct effective addresses, work with arrays and buffers, and use the x86-64 stack for temporary storage, procedure calls, local variables, and stack frames.

THE CORE IDEA

Memory is not the same thing as a register.

One of the most important concepts in assembly programming is understanding the difference between a value, an address, and the memory location represented by that address.

Registers provide the processor with very fast working storage. Main memory provides a much larger space for storing instructions and data. Assembly instructions can operate directly on registers, use immediate values, or access data stored in memory.

Consider the following instruction:

mov rax, rbx

This copies the value currently held in RBX into RAX. It does not access the memory location whose address might happen to be stored in RBX.

Now compare it with:

mov rax, [rbx]

Here the square brackets indicate a memory operand. The value in RBX is interpreted as an address, and the processor reads the data stored at that memory location.

← Return to the complete NASM guide

VALUES & ADDRESSES

The distinction that makes memory addressing easier.

An address tells the processor where something is located. A value is the data stored there or the data being manipulated.

Value

A value is the actual data being used by the program. It might represent an integer, character, pointer, flag, part of an object, or some other piece of information.

mov rax, 42

Here, the immediate value 42 is placed directly into RAX.

Address

An address identifies a location in memory. A register can contain such an address and then be used as part of a memory operand.

mov rax, [rbx]

In this example, the contents of RBX are treated as the address from which data should be loaded.

ADDRESSING FORMS

How NASM constructs memory operands.

x86-64 provides flexible ways to calculate effective addresses. These forms are particularly useful when working with arrays, structures, buffers, pointers, and stack frames.

Immediate values

mov rax, 42

The instruction contains the actual value that should be used. No memory access is involved in this example.

Register operands

mov rax, rbx

The instruction operates directly on values held in registers.

Memory operands

mov rax, [rbx]

The register contains an address, and the square brackets tell NASM to access the memory located at that address.

Base + displacement

mov rax, [rbx + 8]

An offset is added to a base address to access a different memory location.

Base + index

mov rax, [rbx + rcx]

Two registers can contribute to the effective address, which is useful when traversing data structures.

Base + index × scale

mov rax, [rbx + rcx*8]

A scaled index is particularly useful when accessing arrays of elements with a fixed element size.

NASM SYNTAX

What do square brackets mean?

In NASM syntax, square brackets are a critical visual clue that the instruction is accessing memory rather than simply using the value contained in a register.

mov rax, rbx
mov rax, [rbx]

Without brackets

MOV RAX, RBX copies the value contained in RBX into RAX.

With brackets

MOV RAX, [RBX] uses the contents of RBX as a memory address and loads the value stored there.

This distinction is fundamental. Many early assembly mistakes come from treating a pointer value as though it were the data stored at that pointer.

EFFECTIVE ADDRESS

Base, index, scale and displacement.

A memory operand can be constructed from several components, allowing programs to calculate the location of an element efficiently.

A common x86-64 addressing form can be represented conceptually as:

base + index × scale + displacement

The base register provides a starting address. The index register can represent an element position. The scale accounts for the size of each element, and the displacement provides an additional fixed offset.

mov rax, [rbx + rcx*8]

If RBX represents the beginning of an array and RCX represents an element index, multiplying the index by eight is appropriate when each element occupies eight bytes. The resulting effective address identifies the requested element.

ARRAYS

Using memory addressing to traverse arrays.

Arrays provide one of the clearest practical examples of why base, index, and scale addressing are useful.

Suppose an array contains 64-bit integer values. Each element occupies eight bytes. If a register contains the address of the first element, the address of another element can be calculated using its index multiplied by eight.

; RBX = address of array
; RCX = element index

mov rax, [rbx + rcx*8]

If RCX is zero, the first element is accessed. If RCX is one, the address advances by eight bytes. If RCX is two, it advances by sixteen bytes, and so on.

This pattern appears throughout low-level programming and is particularly useful when studying arrays, pointers, compiled code, and data structures.

LEA

LEA calculates an address without loading the data.

LEA, or Load Effective Address, is frequently misunderstood because its name suggests a memory load. It actually calculates an address and places that address into a register.

lea rax, [rbx + rcx*8]

The instruction above calculates the effective address represented by the expression inside the brackets and places that address into RAX. It does not load the value stored at that address.

This makes LEA useful for address calculations and certain arithmetic expressions where the addressing hardware can efficiently construct the desired result.

mov rax, [rbx + rcx*8]
lea rdx, [rbx + rcx*8]

The first instruction accesses memory and retrieves the value. The second calculates the address itself. Understanding this distinction is essential when reading compiler-generated assembly.

OPERAND SIZE

The processor needs to know how much data to access.

A memory address alone does not always tell an instruction whether it should access one byte, two bytes, four bytes, or eight bytes.

Assembly programs frequently work with values of different widths. The instruction and its operands must therefore provide enough information for the processor to determine the intended data size.

Common widthTypical register formExample concept
8-bitALByte-sized data
16-bitAXWord-sized data
32-bitEAXDoubleword-sized data
64-bitRAXQuadword-sized data

Understanding operand size becomes particularly important when working with arrays, structures, strings, binary data, and mixed-width calculations.

THE X86-64 STACK

The stack is a structured region of memory.

The stack provides temporary storage and plays a central role in procedure calls, saved state, return addresses, local variables, and stack frames.

Unlike a general-purpose data structure, the stack follows a particular access discipline. Data is typically added and removed according to a last-in, first-out pattern.

In x86-64, RSP is the stack pointer. It tracks the current stack position, while instructions such as PUSH and POP adjust the stack as data is added or removed.

push rax
push rbx

pop rbx
pop rax

In the example, the values are restored in reverse order. The last value pushed is the first value popped.

STACK COMPONENTS

RSP, RBP, PUSH, POP, CALL and RET.

These concepts should be learned together because they describe different parts of the same procedure and stack-management mechanism.

RSP

The stack pointer identifies the current stack position and changes as data is pushed onto or removed from the stack.

RBP

RBP can provide a stable reference point within a stack frame and is commonly used when explaining local variables and procedure layouts.

PUSH

PUSH places a value on the stack and updates the stack pointer accordingly.

POP

POP retrieves a value from the stack and updates the stack pointer accordingly.

CALL

CALL transfers execution to a procedure and works with the stack to preserve a return address.

RET

RET returns execution to the saved return location associated with a procedure call.

STACK OPERATIONS

How PUSH and POP change the stack.

PUSH and POP provide a straightforward mechanism for saving and restoring values during program execution.

mov rax, 100
mov rbx, 200

push rax
push rbx

pop rcx
pop rdx

The values are pushed in the order RAX and then RBX. Because the stack is last-in, first-out, POP places RBX's saved value into RCX and RAX's saved value into RDX.

This simple pattern demonstrates why stack order matters when saving registers or temporary values.

PROCEDURES

CALL and RET connect control flow with the stack.

Procedure calls require the program to remember where execution should continue after the procedure finishes.

call calculate

; execution continues here
; after calculate returns

calculate:
    ; procedure instructions
    ret

CALL transfers execution to the procedure and preserves a return location. RET then uses the saved return information to continue execution after the original CALL.

The exact stack layout around a procedure depends on the architecture, calling convention, compiler, optimisation choices, and the procedure's own instructions. This is why understanding the stack is so important when debugging or analysing compiled programs.

STACK FRAMES

How procedures organise local data.

A stack frame provides a useful conceptual model for understanding local variables, saved registers, parameters, and return information associated with a procedure.

A traditional stack-frame model often uses RBP as a stable reference point and RSP to track the current top of the stack. Local variables and saved state can then be accessed at known offsets.

push rbp
mov rbp, rsp

; procedure body

mov rsp, rbp
pop rbp
ret

This is a simplified educational model of a procedure prologue and epilogue. Modern compilers may omit the frame pointer or organise the stack differently, especially when optimisation is enabled.

Nevertheless, understanding this conventional model provides a strong foundation for reading assembly and examining stack frames in a debugger.

LOCAL DATA

Accessing local variables through stack offsets.

Once a stack frame has been established, fixed offsets can be used to access data associated with the current procedure.

push rbp
mov rbp, rsp

sub rsp, 16

mov qword [rbp-8], 42
mov rax, [rbp-8]

mov rsp, rbp
pop rbp
ret

The example reserves stack space and stores a value at an offset relative to RBP. The value is then loaded back into RAX.

The exact layout of a real compiled function can be more complicated, but the example demonstrates the fundamental relationship between a stack frame, an address, and a local memory location.

COMMON MISTAKES

Where beginners often get memory and stack concepts wrong.

Most difficulties come from mixing up values, addresses, operand sizes, and stack state.

Forgetting the brackets

Confusing MOV RAX, RBX with MOV RAX, [RBX] changes whether the instruction copies a register value or accesses memory.

Using the wrong element scale

Array addressing depends on element size. An incorrect scale can cause the program to access the wrong memory location.

Ignoring operand width

Reading or writing the wrong number of bytes can corrupt data or produce results that do not match expectations.

Losing track of RSP

Every stack operation changes the stack state. An incorrect push/pop sequence can make procedure returns and saved registers fail.

Assuming RBP is always required

RBP is useful for learning stack frames, but optimised programs may use it differently or omit a traditional frame pointer altogether.

Confusing LEA with a memory load

LEA calculates an effective address. It does not retrieve the value stored at that address.

PUTTING IT TOGETHER

Memory addressing and the stack work together.

The same concepts appear repeatedly in real programs: addresses identify data, registers hold working values, and the stack provides temporary storage and procedure state.

push rbp
mov rbp, rsp

sub rsp, 16

mov qword [rbp-8], 25
mov rax, [rbp-8]

add rax, 5

mov rsp, rbp
pop rbp
ret

This simplified example combines several concepts from the guide. A stack frame is established, local space is reserved, a value is stored in memory, that value is loaded into a register, arithmetic is performed, and the stack frame is removed before returning.

Being able to trace each of these steps is a key milestone in understanding x86-64 assembly.

CONTINUE LEARNING

Continue through the NASM technology cluster.

The remaining guides connect memory and stack concepts with registers, Linux system calls, and debugging.

ACADEMIC & TECHNICAL WORK

Why memory and stack concepts matter.

Memory addressing and stack management are fundamental topics in computer architecture, operating systems, systems programming, cybersecurity, and reverse engineering.

Academic work involving NASM may require students to explain how data is represented in memory, demonstrate addressing techniques, trace stack operations, analyse procedure calls, or document how local variables and parameters are handled.

These concepts also provide a foundation for understanding compiled programs and low-level behaviour during debugging and binary analysis.

ProjectAssignments provides structured technical and academic guidance for complex computing work, with an emphasis on understanding the concepts and reasoning behind the work.

Explore our technical academic services →

Let's make your work clearer

Bring us the difficult part.

Tell us what you're researching, building, or trying to understand. We'll help you find the clearest ethical next move.

Get Guidance
Chat with us on WhatsApp