Confessions of a Code Addict

Confessions of a Code Addict

x86 Addressing Modes, Part 1 — Immediate and Direct Access

The foundations of memory access: static allocation, addressing modes, and the first steps toward low-level thinking.

Abhinav Upadhyay's avatar
Abhinav Upadhyay
Nov 12, 2025
∙ Paid

Welcome back to our series on x86 assembly programming. If you are new, you can check out the series overview.

A Programmer’s Guide to x86-64 Assembly (Series Overview)

A Programmer’s Guide to x86-64 Assembly (Series Overview)

Abhinav Upadhyay
·
Jul 16
Read full story

So far, we have learned the fundamentals of instructions and registers in x86 assembly. But writing real-world programs requires memory access, so we must learn how to deal with memory. If you can master this topic, you level up as a programmer.

There are two kinds of memory where we can keep our program’s data: registers and main memory. We have already learned about using registers; they are the fastest possible memory units in the hardware. But they are very limited in numbers, while real-world code needs much more memory than that.

Apart from that, registers can only hold primitive type values. The integer registers (the 16 general-purpose ones we learned about) handle integers, while separate floating-point registers exist in x86 for floating-point operations. However, we need a way to store and access composite types, such as arrays and structs, which is only possible using main memory.

Accessing memory in assembly is a big topic, so we’ll split it into a multipart series covering each addressing mode step by step as there are several memory addressing modes, and learning to effectively use each of them is crucial for us to read and write assembly code. So, I am going to split this topic into a multipart series. In this first part, we will cover the following topics:

  • Regions of memory in a process’s address space: stack, heap, and data

  • Immediate addressing mode

  • Direct addressing mode

In future parts, we will cover the following:

  • Indirect addressing mode

  • Offset-based addressing mode

  • Indexed addressing mode

We’ll start by understanding how data is organized in memory before we explore addressing modes. Now, let’s dive in!

I’m also publishing this in the form an ebook (PDF). If you don’t wish to upgrade to a subscription, you can purchase the PDF using the following link. If you are a paid subscriber, you can get it at a discount (monthly subs: 20% and annual subs: 50%). Please email me for the discounted link.

Purchase PDF


Regions of Memory in Process Address Space

When programming in high-level languages, you would have learned about the concept of scope or the lifetime of a variable. For example, a global variable lives for the duration of the program; local variables are automatically destroyed when the function returns. And, you can dynamically allocate memory on the heap that lives until it is freed.

When programming in assembly, we need similar scopes. However, there is no compiler to help us out, so we must do it ourselves. These scopes can be achieved by storing data in different regions in the address space of the process. So, we must start there.

There are three main regions in the process’s address space where you can decide to store your program’s data, as shown in the following diagram.

Key regions in the address space of a process: stack, heap, and data
Key regions in the address space of a process: stack, heap, and data
  • Stack segment: The stack segment is primarily used to implement function calls and to store function local data, such as variables and arguments. We will learn to use the stack when we talk about functions in assembly.

  • Data Segment: The data segment is used to store static data. For example, whenever you create global variables or constants in your programs, the compiler may put them in the data segment. The advantage of the data segment is that it is burned as part of the program binary and loaded during startup. As a result, there is no memory allocation overhead at runtime.

  • Heap Segment: The heap segment is used for dynamic memory allocation at runtime. For example, when growing an array, or creating nodes for a tree or a linked list.

In this article, we will mostly use the data segment, and we’ll cover heap and stack in future articles on dynamic memory allocation and function calls.

But, before jumping to memory access modes, we should spend a few minutes to learn how to do static memory allocation in the .data section, as we will be using static memory throughout the rest of this article.

Static Memory Allocation in the .data section

The data segment in the process’s address space is populated based on the contents in the .data section of the executable binary. When we want to create static data in our program, such as global variables or constants, we can put them in the .data section of our program.

To create a static value in the .data section, we need to do three things:

  • Create a label: At the time of writing assembly, we don’t know the exact memory address of the values or instructions, so we must use labels. At linking time, the linker replaces labels with the final addresses in the object code that it generates. So, creating a label for the value gives us a way to refer to its address.

  • Declare the size: We need to tell the assembler the size of the value, so that it can create that much space in the .data section. If you read the article on registers, you may recall that we have the following sizes:

    • .quad: For 8-byte values

    • .long: For 4-byte values

    • .word: For 2-byte values

    • .byte: For single-byte values

    • Apart from these, we also have the .asciz macro to create a nul-terminated ASCII string.

  • Declare the value: Finally, provide the value.

The following example shows how we can create an 8-byte integer value in the .data section with the label ANSWER_TO_LIFE:

Syntax for allocating data in the .data section
Syntax for allocating data in the .data section

This example allocates a single 64-bit value, but it is also possible to create more complex structures. For instance, we can create a struct-like object as shown in the example below:

This series is exclusively for the paid subscribers. Their support keeps this publication sustainable. To access this series and other exclusive content, please consider upgrading to a paid subscription

This post is for paid subscribers

Already a paid subscriber? Sign in
© 2025 Abhinav Upadhyay
Publisher Privacy ∙ Publisher Terms
Substack
Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture