現在のブログ
ゲーム開発ブログ (2025年~) Gamedev Blog (2025~)
レガシーブログ
テクノロジーブログ (2018~2024年) リリースノート (2023~2025年) MeatBSD (2024年)

【Assembly Language】Explaining the For Loop
Previously, I taught how to learn assembly by decompiling C programs into assembly and converting them back to C.
Inspired by this, I decided to write a shorter, more focused series of articles to teach the basics of assembly.
As a reminder, most people reading this article likely have an x86_64 (Intel or AMD) computer (unless you're a poor smartphone slave).
Therefore, the assembly code will be written for x86_64.
This time, we'll cover how to write a for loop.
While Loop
Before that, let's review the while loop introduced in the previous article.
2016ca: 8b 45 f8 mov eax, dword ptr [rbp - 0x8]
2016cd: 3b 45 ec cmp eax, dword ptr [rbp - 0x14]
2016d0: 7f 36 jg 0x201708 <main+0x68>
...
201706: eb c2 jmp 0x2016ca <main+0x2a>
As you may know, this is equivalent to the following in C:
while (fahr <= upper) {
...
}
However, this assembly code is written this way because it was decompiled from C code using objdump
.
When writing assembly manually, a while loop in NASM would look like this:
section .data
section .rodata
fahr: dq 0 ; dq = 64-bit
; dd = 32-bit
; dw = 16-bit
; db = 8-bit
upper: dq 300
section .text
global _start
_start:
while_loop:
mov rax, [fahr]
cmp rax, [upper]
jg end_loop
continue_loop:
...
inc qword [fahr]
jmp while_loop
end_loop:
mov rax, 1
mov rbx, 0
syscall
Note that I chose NASM as the assembler.
Unlike high-level languages like C, assembly varies not only by architecture or OS but also by the assembler used.
Thus, the syntax for assembling with gas
(GNU Assembler) differs from fasm
(Flat Assembler), masm
(Microsoft Macro Assembler), tasm
(Borland's Turbo Assembler), or nasm
(Netwide Assembler).
From here on, I'll use NASM.
This is because NASM is available on all OSes, supports multiple architectures, is widely used, and has been actively maintained for about 30 years (version 3.00 was released just 4 days ago!).
Also, if you're using Linux or BSD, NASM is probably already installed.
Even if it's not, it's almost certainly available in your distribution or OS's repository.
Furthermore, NASM is BSD-licensed, making it freely usable for both open-source and proprietary code, which is ideal for game developers.
It's also worth noting that .rodata
is for initialized constant variables, .data
is for mutable variables, and .bss
is for reserving memory for uninitialized variables.To reserve space for an uninitialized variable in 64-bit, you'd define it as fahr: resq 1
.
In assembly, there's no distinction between int
, float
, string
, bool
, etc.—everything is handled as bits.
However, anyone who has programmed in C (which I assume readers of my assembly articles have) knows you need to manage memory manually.
That's why.
For Loop
Now that the while loop explanation is done, from here on, I'll write assembly code manually instead of writing in C and decompiling.
Also, in assembly, it's standard to comment each line by default, so I encourage you to adopt this habit.
section .rodata
hello: db 'こんにちは、世界',10 ; With newline code
; UTF-8 string,
; 10 = \n
helloLen: equ $-hello ; Length of the string,
; $ = current address,
; -hello = minus the starting
; address of the string
section .text
global _start
_start:
mov rbx, 0 ; Initialize loop counter (int i = 1;)
begin_loop:
cmp rbx, 5 ; Compare i with 5 (i < 5;)
jge end_loop ; Exit loop if i >= 5
continue_loop:
mov rax, 4 ; System call number for write in FreeBSD
mov rdi, 1 ; File descriptor 1 (standard output)
mov rsi, hello ; Address of the string
mov rdx, helloLen ; Length of the string
syscall ; Call the kernel
inc rbx ; Increment loop counter (i++)
jmp begin_loop ; Return to the start of the loop
end_loop:
mov rax, 1 ; System call number for exit in FreeBSD
mov rbx, 0 ; Exit status 0 (success)
syscall ; Call the kernel
Note that system call numbers are specific to FreeBSD.
For Linux, use the following:
section .rodata
hello: db 'こんにちは、世界',10 ; With newline code
; UTF-8 string,
; 10 = \n
helloLen: equ $-hello ; Length of the string,
; $ = current address,
; -hello = minus the starting
; address of the string
section .text
global _start
_start:
mov rbx, 0 ; Initialize loop counter (int i = 1;)
begin_loop:
cmp rbx, 5 ; Compare i with 5 (i < 5;)
jge end_loop ; Exit loop if i >= 5
continue_loop:
mov rax, 1 ; System call number for write in Linux
mov rdi, 1 ; File descriptor 1 (standard output)
mov rsi, hello ; Address of the string
mov rdx, helloLen ; Length of the string
syscall ; Call the kernel
inc rbx ; Increment loop counter (i++)
jmp begin_loop ; Return to the start of the loop
end_loop:
mov rax, 60 ; System call number for exit in Linux
mov rbx, 0 ; Exit status 0 (success)
syscall ; Call the kernel
This assembly code is equivalent to the following C code:
#include <stdio.h>
int main() {
for (int i = 0; i < 5; i++) {
printf("こんにちは、世界\n");
}
return 0;
}
In assembly, you don't compile the code; you first assemble it into an object file and then link it into a binary file.
This shows just how low-level it is!
To assemble and link, you need the following two commands:
$ nasm -f elf64 for.asm -o for.o
$ ld for.o -o for
To assemble and link, you need the following two commands:
$ ./for
こんにちは、世界
こんにちは、世界
こんにちは、世界
こんにちは、世界
こんにちは、世界
Pretty simple, right?
That's all