Kata kunci while dan for tidak memiliki padanan berupa satu instruksi di tingkat mesin. Perulangan tetap tersusun dari bagian yang sama seperti pada bahasa tingkat tinggi, yaitu inisialisasi, kondisi, tubuh, dan kenaikan, tetapi setiap bagian ditulis sebagai instruksi biasa dengan label sebagai penanda titik ulangnya. Topik ini membangun perulangan secara manual dari label, perbandingan, dan jump mundur, menampilkan penjumlahan bilangan 1 sampai 100 dengan hasil yang dibaca dari debugger, serta menimbang instruksi loop sebagai alternatif yang jarang dipilih compiler modern.
Empat bagian perulangan manual#
Perulangan manual memerlukan empat bagian, yaitu inisialisasi yang menetapkan nilai awal, kondisi yang menentukan apakah perulangan berlanjut atau berhenti, tubuh yang menjalankan pekerjaan, dan kenaikan yang menggerakkan penghitung menuju kondisi berakhir. Label memegang peran kurung kurawal pada bahasa tingkat tinggi. Jump mundur, yaitu jump yang menunjuk label dengan alamat lebih rendah, mengembalikan kendali ke atas tubuh, sedangkan jump ke label selesai membawa kendali keluar dari perulangan.
Kerangka perulangan naik berikut menempatkan kondisi di awal tubuh, sehingga nilai yang melewati batas tidak pernah ikut diproses.
mov rcx, 1 ; inisialisasi
.ulang:
cmp rcx, 100 ; kondisi
ja selesai ; melewati batas berarti berhenti
add rax, rcx ; tubuh
add rcx, 1 ; kenaikan
jmp .ulang ; kembali ke kondisi
selesai:Label yang diawali titik seperti .ulang bersifat lokal terhadap label global terdekat, sehingga kerangka yang sama dapat digunakan di beberapa prosedur tanpa bentrok nama. Keempat bagiannya juga terbaca jelas pada fragmen itu, yaitu mov sebagai inisialisasi, cmp dan ja sebagai kondisi, add rax, rcx sebagai tubuh, dan add rcx, 1 sebagai kenaikan. Dalam kasus lain, bagian kenaikan dapat berupa penurunan, dan kondisinya menyesuaikan arah gerak penghitung.
Menjumlahkan 1 sampai 100#
Contoh berikut menggunakan arah yang berlawanan dengan kerangka naik, yaitu penghitung menurun dari 100 menuju nol. Keuntungannya, kondisi berakhir sudah tersedia tanpa perbandingan tambahan, karena sub menetapkan ZF pada saat penghitung mencapai nol, sehingga jne cukup membaca flag itu. Keempat bagian tetap lengkap, tetapi kondisinya berpindah ke akhir tubuh, tepat pada jump mundurnya.
; jumlah.asm
; Menjumlahkan bilangan 1 sampai 100 dengan perulangan manual.
; Penghitung menurun dari 100 menuju nol sehingga jne
; cukup membaca ZF yang ditetapkan sub.
default rel
section .text
global _start
_start:
mov rax, 0 ; inisialisasi, akumulator nol
mov rcx, 100 ; inisialisasi, penghitung 100
.ulang:
add rax, rcx ; tubuh, tambahkan penghitung ke akumulator
sub rcx, 1 ; penurunan, kurangi penghitung satu
jne .ulang ; kondisi, ulangi selama rcx belum nol
selesai:
mov edi, eax
mov eax, 60
syscall# rakit, tautkan, dan jalankan
nasm -f elf64 jumlah.asm -o jumlah.o && ld jumlah.o -o jumlah && ./jumlah; echo "exit=$?"
# baca nilai penuh akumulator di debugger
gdb -batch -ex 'break selesai' -ex 'run' -ex 'info registers rax rcx' ./jumlahexit=186
Breakpoint 1 at 0x401013
Breakpoint 1, 0x0000000000401013 in selesai ()
rax 0x13ba 5050
rcx 0x0 0Akumulator rax benar berisi 5050, yaitu 0x13ba, pada saat kendali tiba di selesai, dan rcx sudah mencapai nol sesuai kondisi berakhirnya. Menjalankan ./jumlah secara langsung hanya menampilkan exit=186 karena kode keluar hanya berukuran 8 bit, dan 5050 dibagi 256 menyisakan 186. Nilai penuhnya tetap utuh di register sampai syscall exit dijalankan, dan debugger memperlihatkannya sebagai pasangan 0x13ba dan 5050 pada baris rax. Angka pada baris rcx, yaitu 0x0 dan 0, menegaskan bahwa perulangan berhenti tepat setelah penghitung habis. Pola membaca hasil dari register melalui debugger menjadi kebiasaan yang berguna karena kode keluar memang sempit.
Instruksi loop sebagai alternatif#
Instruksi loop memadatkan dua langkah versi manual menjadi satu, yaitu menurunkan rcx sebanyak satu dan melompat mundur selama rcx belum nol. Program penjumlahan yang sama dapat ditulis ulang sebagai berikut.
; jumlah-loop.asm
; Versi penjumlahan yang sama dengan memakai instruksi loop.
; loop menurunkan rcx dan melompat mundur selama rcx belum nol.
default rel
section .text
global _start
_start:
mov rax, 0 ; akumulator nol
mov rcx, 100 ; penghitung 100
.ulang:
add rax, rcx ; tubuh
loop .ulang ; turunkan rcx, ulangi selama belum nol
selesai:
mov edi, eax
mov eax, 60
syscall# rakit, tautkan, dan jalankan versi loop
nasm -f elf64 jumlah-loop.asm -o jumlah-loop.o && ld jumlah-loop.o -o jumlah-loop && ./jumlah-loop; echo "exit=$?"exit=186Hasilnya identik dengan versi manual. Meskipun benar dan lebih ringkas, loop jarang dipilih compiler modern. Alasannya praktis, yaitu pada banyak prosesor x86-64 modern instruksi ini berjalan lebih lambat daripada pasangan sub dan jne yang terpisah, sehingga compiler cenderung menghasilkan bentuk manual untuk perulangan dengan tubuh yang pendek. Instruksi loop tetap perlu dikenal karena muncul pada kode lama dan pada hasil disassembly program yang sudah jadi. Strukturnya juga mengikat penghitung pada rcx secara khusus, sedangkan bentuk manual bebas memilih register mana pun, misalnya ketika rcx sudah terpakai oleh perulangan luar pada struktur bersarang.
Kesalahan umum#
Dua kesalahan paling lazim pada perulangan manual menyangkut penghitungnya.
Penghitung yang tidak bergerak. Menghapus sub rcx, 1 pada jumlah.asm membuat rcx selalu 100, sehingga jne selalu terpenuhi dan program tidak pernah tiba di syscall exit. Program yang dijalankan di terminal tampak membeku, dan satu-satunya jalan untuk berhenti adalah sinyal interrupt dari luar. Perbaikannya adalah memastikan setiap jalur di dalam tubuh menggerakkan penghitung menuju kondisi berakhir.
Batas yang meleset satu. Menulis cmp rcx, 99 pada kerangka naik membuat angka 100 tidak pernah dijumlahkan, sedangkan batas 101 memasukkan angka 101. Hasilnya menjadi 4950 atau 5151 alih-alih 5050. Perbaikannya adalah menguji tepi batas dengan memastikan nilai pertama dan nilai terakhir yang diinginkan benar-benar masuk ke tubuh, satu demi satu.
Ringkasan#
- Perulangan manual tersusun dari inisialisasi, kondisi, tubuh, dan kenaikan, dengan label sebagai titik ulang dan jump mundur sebagai pengembali kendali.
- Penghitung menurun memanfaatkan ZF dari
sub, sehingga kondisi berakhirnya cukup diperiksa olehjnetanpacmptambahan, dan jumlah 1 sampai 100 tampak sebagai0x13baatau 5050 di debugger. - Kode keluar hanya berukuran 8 bit, sehingga 5050 tampil sebagai 186 pada
exit=, dan nilai penuhnya harus dibaca dari register. - Instruksi
loopmemadatkan penurunan dan lompatan menjadi satu, tetapi compiler modern cenderung memilih bentuk manual karena latensi pasangansubdanjnelebih rendah.
Referensi#
- NASM: The Netwide Assembler manual, versi 2.16.03 — NASM — Rujukan instruksi LOOP, Jcc, dan label lokal pada bahasa NASM.
- Intel 64 and IA-32 Architectures Software Developer Manuals — Intel — Volume 2 memuat perilaku instruksi LOOP beserta semantik penghitungnya.