Writing immediates to memory

Wed 02 September 2026

An immediate in assembly is a scalar value such as 123. While the concept is familiar from high-level languages, immediates require more attention in x86-64 assembly. There are no floating-point immediates, so everything below is about integers.

Memory access

Writing 123 immediate value to a memory address like so mov [rdi], 123 can catch us off guard.

﹩ cat > demo.s <<ASM
.intel_syntax noprefix
.text
.global _demo
_demo:
    mov [rdi], 123
    ret
ASM

﹩ zig build-obj demo.s
error(compilation): clang failed with stderr: demo.s:5:9: error: ambiguous operand size for instruction 'mov'
    mov [rdi], 123
        ^~~~

The LLVM assembler behind zig build-obj tells us that our mov instruction has an ambiguous operand size (if the example seems confusing, try my earlier post on using Assembly with Zig). Indeed, the assembler is not sure how many bytes to use to represent 123 in memory. It can use a byte, a word, a double word, and so on. Interestingly, NASM v3.02 defaults to using a byte size and truncates larger values with a warning.

﹩ cat > demo.asm <<ASM
section .text
global _demo
_demo:
    mov [rdi], 123
    ret
ASM

﹩ nasm -f macho64 demo.asm
﹩ objdump -d --x86-asm-syntax=intel demo.o
       0: c6 07 7b                      mov byte ptr [rdi], 0x7b
       3: c3                            ret

Let's see how our mov [rdi], 123 is different from disassembled mov byte ptr [rdi], 0x7b:

  • byte ptr in front of [rdi] means to treat the memory address in register rdi as a pointer to a byte size integer, e.g., i8 or u8
  • 0x7b is 123 in hex

The LLVM assembler has no problems building the object file demo.o once we add byte ptr. The objdump confirms that zig build-obj emits the same machine code for the _demo() function as NASM.

﹩ cat > demo.s <<ASM
.intel_syntax noprefix
.text
.global _demo
_demo:
    mov byte ptr [rdi], 123
    ret
ASM

﹩ zig build-obj demo.s
﹩ objdump -d --x86-asm-syntax=intel demo.o
       0: c6 07 7b                      mov byte ptr [rdi], 0x7b
       3: c3                            ret

We've got the function in demo.o file, let's make sure it actually works. Here is a Zig program that declares a byte size variable var v: i8 = 0 and passes its pointer when calling demo(&v). The assembly function _demo() writes 123 to that dereferenced pointer and returns. Finally, we print the variable v and its raw memory with std.mem.asBytes(&v). If you're unsure how it all works, see The C ABI: Using Assembly with Zig post.

﹩ cat > main.zig <<ZIG
const std = @import("std");

extern fn demo(v: *i8) void;

pub fn main() void {
    var v: i8 = 0;
    demo(&v);
    std.debug.print("{d} is stored as 0x{x} at {*}\n", .{
        v,
        std.mem.asBytes(&v),
        &v,
    });
}
ZIG

﹩ zig build-exe main.zig demo.s
﹩ ./main
123 is stored as 0x7b at i8@7ff7b00a2000

As expected, the program's output shows that 123 was stored as 0x7b. It also printed the memory address 0x7ff7b00a2000 where the value was placed. What if we wanted to use 4 bytes of memory instead of just one byte? We would need to replace byte with dword in the mov instruction like this mov dword ptr [rdi], 123, and update main.zig to use i32 instead of i8.

🔻 mov dword ptr [rdi], 123
﹩ cat > demo.s <<ASM
.intel_syntax noprefix
.text
.global _demo
_demo:
    mov dword ptr [rdi], 123
    ret
ASM

﹩ cat > main.zig <<ZIG
const std = @import("std");

extern fn demo(v: *i32) void;

pub fn main() void {
    var v: i32 = 0;
    demo(&v);
    std.debug.print("{d} is stored as 0x{x} at {*}\n", .{
        v,
        std.mem.asBytes(&v),
        &v,
    });
}
ZIG

﹩ zig build-exe main.zig demo.s
﹩ ./main
123 is stored as 0x7b000000 at i32@7ff7b4fe9000

The updated program reported that it stored 0x7b as 0x7b000000 (not 0x0000007b) at 0x7ff7b4fe9000 address. This is because 123 immediate's bytes 00 00 00 7b are arranged in little-endian order 7b 00 00 00: the least significant byte 7b is placed at the smallest memory address 7ff7b4fe9000.

   Address     Byte
7ff7b4fe9003    00  ← most significant byte
7ff7b4fe9002    00
7ff7b4fe9001    00
7ff7b4fe9000    7b  ← least significant byte

Two's complement

What if we store a negative immediate value mov byte ptr [rdi], -123?

🔻 mov byte ptr [rdi], -123
﹩ cat > demo.s <<ASM
.intel_syntax noprefix
.text
.global _demo
_demo:
    mov byte ptr [rdi], -123
    ret
ASM

﹩ cat > main.zig <<ZIG
const std = @import("std");

extern fn demo(v: *i8) void;

pub fn main() void {
    var v: i8 = 0;
    demo(&v);
    std.debug.print("{d} is stored as 0x{x} at {*}\n", .{
        v,
        std.mem.asBytes(&v),
        &v,
    });
}
ZIG

﹩ zig build-exe main.zig demo.s
﹩ ./main
-123 is stored as 0x85 at i8@7ff7b8653000

The -123 value got stored as 0x85 in memory. This is due to two's complement method of representing a signed integer (invert all bits and add one):

  • 0111 1011 = 0x7b -- 123 in binary format
  • 1000 0100 = 0x84 -- invert all bits of 123
  • 1000 0101 = 0x85 -- add one

The Python one liner below confirms it.

﹩ python3 -c 'print("0x7b = {0:0>8b}\n0x85 = {1:0>8b}".format(0x7b, 0x85))'
0x7b = 01111011
0x85 = 10000101

If we store -123 as four bytes mov dword ptr [rdi], -123, we'll get 0x85ffffff memory representation.

🔻 mov dword ptr [rdi], -123
﹩ cat > demo.s <<ASM
.intel_syntax noprefix
.text
.global _demo
_demo:
    mov dword ptr [rdi], -123
    ret
ASM

﹩ cat > main.zig <<ZIG
const std = @import("std");

extern fn demo(v: *i32) void;

pub fn main() void {
    var v: i32 = 0;
    demo(&v);
    std.debug.print("{d} is stored as 0x{x} at {*}\n", .{
        v,
        std.mem.asBytes(&v),
        &v,
    });
}
ZIG

﹩ zig build-exe main.zig demo.s
﹩ ./main
-123 is stored as 0x85ffffff at i32@7ff7b2e8a00c

Converting 0x85ffffff to a big-endian order gives us 0xffffff85:

  • 0000 0000 0000 0000 0000 0000 0111 1011 = 0x7b -- 123 in binary format
  • 1111 1111 1111 1111 1111 1111 1000 0100 = 0xffffff84 -- invert all bits of 123
  • 1111 1111 1111 1111 1111 1111 1000 0101 = 0xffffff85 -- add one
﹩ python3 -c 'print("0xffffff85 = {0:0>32b}".format(0xffffff85))'
0xffffff85 = 11111111111111111111111110000101

Sign extension

How about storing a positive number 2,147,483,648 (2^31) as eight bytes mov qword ptr [rdi], 2147483648?

﹩ cat > demo.s <<ASM
.intel_syntax noprefix
.text
.global _demo
_demo:
    mov qword ptr [rdi], 2147483648
    ret
ASM

﹩ cat > main.zig <<ZIG
const std = @import("std");

extern fn demo(v: *i64) void;

pub fn main() void {
    var v: i64 = 0;
    demo(&v);
    std.debug.print("{d} is stored as 0x{x} at {*}\n", .{
        v,
        std.mem.asBytes(&v),
        &v,
    });
}
ZIG

﹩ zig build-exe main.zig demo.s
error(compilation): clang failed with stderr: demo.s:5:5: error: invalid operand for instruction
    mov qword ptr [rdi], 2147483648
    ^

Zig tooling straight up refuses to build with invalid operand for instruction error, and NASM only warns signed dword exceeds bounds.

﹩ cat > demo.asm <<ASM
section .text
global _demo
_demo:
    mov qword [rdi], 2147483648
    ret
ASM

﹩ nasm -f macho64 demo.asm
demo.asm:4: warning: signed dword exceeds bounds [-w+number-overflow]

﹩ zig build-exe main.zig demo.o
﹩ ./main
-2147483648 is stored as 0x00000080ffffffff at i64@7ff7bf1ff008

The program's output is surprising -2147483648 is stored as 0x00000080ffffffff:

  • -2147483648 stored value is negative
  • 0x00000080ffffffff memory representation has four 0xff bytes whereas we expect four zeros 0x0000008000000000

Let's transform 0x00000080ffffffff to a big-endian order 0xffffffff80000000:

  • 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 = 00 00 00 00 80 00 00 00 -- 2147483648 in binary format
  • 1111 1111 1111 1111 1111 1111 1111 1111 0111 1111 1111 1111 1111 1111 1111 1111 = ff ff ff ff 7f ff ff ff -- invert all bits of 2147483648
  • 1111 1111 1111 1111 1111 1111 1111 1111 1000 0000 0000 0000 0000 0000 0000 0000 = ff ff ff ff 80 00 00 00 -- add one
﹩ python3 -c 'print("0xffffffff80000000 = {0:0>64b}".format(0xffffffff80000000))'
0xffffffff80000000 = 1111111111111111111111111111111110000000000000000000000000000000

Let's look at the machine code NASM produced.

﹩ objdump -d --x86-asm-syntax=intel demo.o
       0: 48 c7 07 00 00 00 80          mov qword ptr [rdi], -0x80000000
       7: c3                            ret

The machine code shows that despite our 8-byte memory destination, the immediate 00 00 00 80 (in little-endian form) is only 4 bytes. The mov instruction in x86-64 can't write a 64-bit immediate to memory, so the CPU sign-extends those 4 bytes to fill the 8. Our 2147483648 is 0x80000000 and its bit 31 is set, so the CPU fills the upper 32 bits with ones and writes 0xffffffff80000000 which is -2147483648.

The solution is to write to a register first, and then to memory.

﹩ cat > demo.asm <<ASM
section .text
global _demo
_demo:
    mov rax, 2147483648
    mov [rdi], rax
    ret
ASM

﹩ nasm -f macho64 demo.asm

﹩ zig build-exe main.zig demo.o
﹩ ./main
2147483648 is stored as 0x0000008000000000 at i64@7ff7b8227008

Note that no size specifier is needed now because the register rax already tells the assembler that the store is eight bytes wide.

Only the register form of mov accepts a 64-bit immediate, though NASM doesn't even need it here.

﹩ objdump -d --x86-asm-syntax=intel demo.o
       0: b8 00 00 00 80                mov eax, 0x80000000
       5: 48 89 07                      mov qword ptr [rdi], rax
       8: c3                            ret

NASM replaced our mov rax, 2147483648 with mov eax, 0x80000000 which puts 0x0000008000000000 into rax because writing to a 32-bit register eax zero-extends (not sign-extends!) into the full 64-bit register rax. Note that this is specific to 32-bit writes: ax and al leave the upper bits of rax untouched.

The same 4-byte immediate limit also applies to add, cmp, and the other instructions. Anything larger has to go through a register.


References:

Category: Zig Tagged: assembler zig

comments


Hello World in avo 🥑

Tue 02 December 2025

Let's learn together how to write some Go assembly using avo aka writing assembly-like Go code to generate assembly. To make it more clear, here is an avo program add/asm.go.

package main

import asm "github.com/mmcloughlin/avo/build"

func main() {
    asm.TEXT("Add", asm.NOSPLIT, "func(x …

Category: Go Tagged: assembler golang

comments

Read More

Linux process

Tue 31 January 2023

Being curious about BPF, I studied source code of several programs from the BCC libbpf-tools. BPF performance tools book aided me to navigate BPF C code. For example, it explained that a BPF program has to use helpers because it can't access arbitrary memory (outside of BPF) and can't call …

Category: Infrastructure Tagged: architecture linux

comments

Read More

API based on Flask

Mon 09 December 2013

Here I want to consider implementation of API best practices which usually don't follow Fielding's REST strictly. Example Flask project is on GitHub.

API Versioning

Interfaces are changed hence versioning is mandatory in order to not annoy your users. You might need to add new resource or field to particular …

Category: Python Tagged: python flask api

comments

Read More

Preparation to Python Interview

Fri 02 November 2012

I decided to collect a little more information and experience during preparation to Python developer interview. These are some information and links which seemed important to me. Maybe it will be helpful.

How does it usually go?

What kind of projects did you participate in?

What did you do at …

Category: Python Tagged: python interview

comments

Read More

Django TODO: тестирование во время конструирования

Fri 29 June 2012

Тестирование, выполняемое разработчиками -- один из важнейших элементов полной стратегии тестирования.

Тестирование может указать только на отдельные дефектные области программы -- оно не сделает программу удобнее в использовании, более быстрой, компактной, удобочитаемой или расширяемой.

Цель тестирования противоположна целям других этапов разработки. Его целью является нахождение ошибок. Успешным считается тест, нарушающий работу ПО …

Category: Python Tagged: python django django-todo testing

comments

Read More

Django TODO: конструирование системы

Fri 29 June 2012

При работе над проектом конструирование включает другие процессы, в том числе проектирование. Формальная архитектура дает ответы только на вопросы системного уровня, при этом значительная часть проектирования может быть намеренно оставлена на этап конструирования. Проектирование -- это "постепенный" процесс. Проекты приложений не возникают в умах разработчиков сразу в готовом виде. Они развиваются …

Category: Python Tagged: python django django-todo construction

comments

Read More

Django TODO: проектирование архитектуры системы

Fri 29 June 2012

Следующим этапом разработки системы является проектирование архитектуры.

Архитектура должна быть продуманным концептуальным целым. Главный тезис самой популярной книги по разработке ПО "Мифический человеко-месяц" гласит, что основной проблемой, характерной для крупных систем, является поддержание их концептуальной целостности. Хорошая архитектура должна соответствовать проблеме [1].

Разделение системы на подсистемы на уровне архитектуры, позволяет …

Category: Python Tagged: python django django-todo architecture

comments

Read More

Django TODO: выработка требований к системе

Fri 29 June 2012

После прочтения Макконелла захотелось спроецировать его советы на Django. Для этого я взял за основу разработку системы Django TODO. Итак, первый этап -- выработка требований к системе.

Требования подробно описывают, что должна делать система. Внимание к требованиям помогает свести к минимуму изменения системы после начала разработки. Явные требования помогают гарантировать, что …

Category: Python Tagged: python django django-todo requirements

comments

Read More

Соглашения по разработке на Python/Django

Fri 29 June 2012

Во время разработки я часто сверяюсь с известными мне соглашениями, стараюсь следовать рекомендациям. Цитировать их не имеет смысла -- лучше приведу ссылки.

PEP 8 -- Style Guide for Python Code.

Code Like a Pythonista: Idiomatic Python. В нем я нашел ответы на вопросы форматирования длинных строк:

expended_time = (self.finish_date() - self.start_date
                 + datetime …

Category: Python Tagged: python django best practices

comments

Read More

Разделение настроек в Django

Fri 29 June 2012

В Django wiki собраны различные способы разделения настроек. Мне нравится вариант, описанный в блоге Senko Rašić:

settings/
├── __init__.py
├── base.py
├── development.py
├── local.py
└── production.py

base.py содержит общие настройки для development.py и production.py, например:

ADMINS = ()
MANAGERS = ADMINS

TIME_ZONE = 'Asia/Yekaterinburg'
# ...

production.py содержит настройки для …

Category: Python Tagged: python django settings

comments

Read More

Краткий обзор инфраструктуры для разработки reusable Django приложений

Wed 13 June 2012

Начиная впервые разрабатывать веб-приложения на новом фреймворке программист зачастую сталкивается с некоторыми трудностями. При разработке отчуждаемых веб-приложений на Django к этим проблемам необходимо отнести организацию файлов в проекте, обнаружение тестов, вопросы пакетирования приложений и организации автоматизированного тестирования. В данной статье приведены пути решения этих проблем.

Важно знать различия между двумя …

Category: Python Tagged: python django infrastructure

comments

Read More

Вычислительные методы одномерной оптимизации

Wed 06 October 2010

На третьем курсе по предмету методы оптимизации делали лабораторную работу на тему «Вычислительные методы одномерной оптимизации». Задача заключалась в поиске безусловного минимума функции f(x) = pow(x, 3) – x + pow(e, -x) на начальном интервале [0, 1] с точностью 0.00001.

Вычисления производились через:

  • пассивный метод;
  • равномерные блочные методы;
  • метод …

Category: Misc Tagged: php mathematical optimization

comments

Read More

Определение нажатия комбинации клавиш средствами BIOS на ассемблере

Thu 03 December 2009

По учебе понадобилось написать программу на ассемблере, которая должна распознать нажатие «горячей» комбинации клавиш LeftCtrl+RightShift+F3 и реагировать на него звуковым сигналом. Информации/примеров по этой теме маловато, по этому решил опубликовать свою программку.

masm
.model small
.stack 256
.data
    Msg_about db 'Распознать нажатие «горячей» комбинации клавиш', 0Ah, 0Dh …

Category: Misc Tagged: assembler

comments

Read More

Моделирование одноканальной СМО с отказами

Sat 30 May 2009

Дана одноканальная система массового обслуживания с отказами. В нее поступают заявки через промежуток времени n, где n – случайная величина, подчиненная равномерному закону распределения. Время обслуживания заявки системой m также является случайной величиной с показательным законом распределения. Если к моменту прихода заявки канал занят, заявка покидает систему необслуженной.

Изначально код был …

Category: Misc Tagged: python modeling single-channel queue

comments

Read More