Array dalam memori hanyalah deretan byte berurutan tanpa tanda batas antarelemen, sehingga seluruh penelusuran array pada assembly bertumpu pada satu perhitungan alamat. Topik ini membahas perhitungan offset elemen array, penjumlahan seluruh elemen dengan pengalamatan indeks berskala, dan pola penelusuran kedua yang memajukan pointer satu elemen setiap putaran. Hasil akhirnya adalah dua pola penelusuran yang dijalankan pada data yang sama dan memberikan hasil yang sama, lengkap dengan pemeriksaan nilai akhirnya di memori lewat gdb.
Offset elemen array#
Alamat elemen dengan indeks i dihitung dari alamat awal array ditambah i dikalikan ukuran elemennya. Array yang dideklarasikan dengan dq berukuran 8 byte per elemen, sedangkan array dd berukuran 4 byte per elemen, sehingga rumusnya sebagai berikut.
Pada array dq dengan delapan elemen, elemen dengan indeks 3 berada pada offset byte 24, dan elemen terakhir berada pada offset byte 56. Rumus itu diterjemahkan langsung ke dalam bentuk pengalamatan berskala yang dibahas pada mode pengalamatan, dengan skala mengambil peran ukuran elemen.
; tidak-dijalankan
; alamat elemen ke-i pada array dq
mov rax, [suhu + rsi*8] ; baca isi elemen ke-i
lea rdx, [suhu + rsi*8] ; ambil alamat elemen ke-i tanpa membacanyaSkala 8 pada array dq dan skala 4 pada array dd memungkinkan indeks ditulis sebagai nomor elemen, bukan sebagai offset byte. Perbedaan ini penting karena bentuk [suhu + rsi] tanpa skala tetap sah bagi assembler, tetapi maknanya berbeda, yaitu offset byte ke-rsi dan bukan elemen ke-rsi. Pada array byte yang dideklarasikan dengan db, ukuran elemennya 1 byte sehingga indeks dan offset byte menjadi angka yang sama, dan skala 1 dapat ditulis maupun dihilangkan.
Offset yang dihitung dengan rumus ini juga menjadi dasar perhitungan alamat batas, yaitu alamat satu langkah setelah elemen terakhir yang digunakan pola pointer pada bagian berikutnya.
Penjumlahan dengan dua pola penelusuran#
Program berikut menggunakan satu array berisi delapan pembacaan sensor suhu dan menjumlahkannya dengan dua pola berbeda. Pola pertama menggunakan indeks berskala [suhu + r13*8] yang bertambah satu setiap putaran, sedangkan pola kedua memajukan pointer berjalan delapan byte setiap putaran hingga mencapai alamat satu langkah di luar array.
; jumlah-array.asm, penjumlahan array dq dengan indeks berskala dan pointer maju
default rel
JUM equ 8 ; jumlah elemen
UKURAN equ 8 ; ukuran satu elemen dq dalam byte
section .data
suhu dq 27, 29, 31, 30, 28, 26, 27, 30 ; delapan pembacaan sensor
label1 db "elemen[3] = "
len1 equ $ - label1
label2 db "jumlah (indeks) = "
len2 equ $ - label2
label3 db "jumlah (pointer) = "
len3 equ $ - label3
section .bss
hasil resq 1
digit resb 25
section .text
global _start
_start:
mov rbx, 3 ; indeks elemen yang dibaca
mov r8, [suhu + rbx*8] ; alamat = suhu + 3*8, elemen keempat
lea r14, [suhu] ; pointer berjalan, di awal array
lea r15, [suhu + JUM*8] ; batas berhenti, satu langkah di luar array
xor r12, r12 ; penjumlah versi pointer
.pointer_loop:
add r12, [r14]
add r14, 8 ; maju satu elemen, bukan satu byte
cmp r14, r15
jb .pointer_loop
push r12
xor r12, r12 ; penjumlah versi indeks
xor r13, r13 ; indeks elemen
.indeks_loop:
add r12, [suhu + r13*8] ; alamat = suhu + r13*8
inc r13
cmp r13, JUM
jb .indeks_loop
mov [hasil], r12 ; simpan jumlah versi indeks ke .bss
push r12
lea rsi, [label1]
mov rdx, len1
mov rax, r8
call cetak_baris
lea rsi, [label2]
mov rdx, len2
pop rax
call cetak_baris
lea rsi, [label3]
mov rdx, len3
pop rax
call cetak_baris
selesai:
mov rdi, [hasil] ; kode keluar = jumlah seluruh elemen
mov rax, 60
syscall
cetak_baris: ; rsi = alamat label, rdx = panjang label, rax = nilai
push rax
call cetak_teks
pop rax
call cetak_angka
ret
cetak_teks: ; tulis rsi sepanjang rdx byte ke stdout
mov rax, 1
mov rdi, 1
syscall
ret
cetak_angka: ; tulis rax sebagai desimal tanpa tanda diikuti baris baru
mov rbx, 10 ; pembagi
lea r12, [digit + 24]
mov byte [r12], 10 ; byte newline di ujung buffer
lea r12, [digit + 24]
.isi:
xor edx, edx
div rbx
add dl, 48 ; 48 adalah kode ASCII angka 0
dec r12
mov [r12], dl
test rax, rax
jnz .isi
lea rdx, [digit + 25]
sub rdx, r12 ; panjang digit ditambah newline
mov rsi, r12
mov rax, 1
mov rdi, 1
syscall
ret
section .note.GNU-stack noalloc noexec nowrite progbits# rakit dan tautkan program
nasm -f elf64 jumlah-array.asm -o jumlah-array.o
ld jumlah-array.o -o jumlah-array
# jalankan program
./jumlah-array
echo "exit=$?"elemen[3] = 30
jumlah (indeks) = 228
jumlah (pointer) = 228
exit=228Kedua pola menghasilkan jumlah 228, yaitu penjumlahan 27, 29, 31, 30, 28, 26, 27, dan 30, dan nilai itu juga muncul sebagai kode keluar program. Batas berhenti pada pola pointer dihitung dengan lea r15, [suhu + JUM*8], yaitu alamat satu langkah di luar elemen terakhir. Perbandingan jb berhenti tepat sebelum pointer mencapai alamat itu, sehingga tidak ada byte di luar array yang tersentuh.
Pemeriksaan hasil di debugger#
Nilai penjumlahan versi indeks disimpan ke label hasil di section .bss sebelum dicetak, sehingga nilai penuhnya dapat diperiksa langsung di memori. Label selesai tepat sebelum syscall exit menjadi titik henti yang pasti, karena kedua perulangan sudah selesai dijalankan pada saat eksekusi mencapainya. Pada titik henti itu, isi hasil dibaca dengan x/1gx, yaitu satu qword dalam heksadesimal.
# hentikan program tepat sebelum exit dan periksa hasil di memori
gdb -batch -ex 'break selesai' -ex 'run' -ex 'x/1gx &hasil' ./jumlah-arrayBreakpoint 1 at 0x401084
elemen[3] = 30
jumlah (indeks) = 228
jumlah (pointer) = 228
Breakpoint 1, 0x0000000000401084 in selesai ()
0x402074: 0x00000000000000e4Keluaran gdb menampilkan isi hasil sebagai satu qword, yaitu 0x00000000000000e4. Nilai heksadesimal 0xe4 sama dengan 228 desimal, sehingga isi memori cocok dengan yang dicetak oleh program dan dengan perhitungan manual. Pemeriksaan semacam ini memisahkan dua kemungkinan kesalahan, yaitu perulangan yang salah menghitung dan pencetakan yang salah mengonversi, karena isi memori selalu mencerminkan apa yang benar-benar ditulis perulangan.
Kesalahan umum#
Menggunakan perbandingan yang melewati batas. Perulangan indeks dengan cmp r13, JUM dan jle tetap memasuki putaran ketika r13 bernilai 8, sehingga [suhu + 8*8] membaca byte di luar array. Bandingkan dengan jb, atau dengan jl pada bilangan bertanda, agar indeks berhenti sebelum mencapai jumlah elemen.
Melupakan ukuran elemen pada batas pointer. Alamat berhenti untuk array dq dengan delapan elemen adalah suhu + JUM*8 atau byte ke-64, sedangkan suhu + JUM hanya melompat delapan byte sehingga perulangan hanya memproses elemen pertama. Selalu kalikan jumlah elemen dengan ukuran elemen ketika batas dinyatakan sebagai alamat.
Membiarkan penjumlah tidak dinolkan. Register r12 pada program ini dinolkan dengan xor r12, r12 sebelum setiap perulangan dimulai, karena register menyimpan nilai apa pun yang ditinggalkan instruksi sebelumnya. Tanpa penolanan, hasil penjumlahan terakumulasi dengan nilai lama dan kedua pola langsung menunjukkan angka yang berbeda meskipun keduanya menelusuri array yang sama.
Ringkasan#
- Alamat elemen dengan indeks i dihitung sebagai basis ditambah i dikalikan ukuran elemen, dan skala pada pengalamatan berskala mengambil peran ukuran elemen tersebut.
- Penelusuran dengan indeks menggunakan bentuk
[array + rsi*8]untuk arraydq, sedangkan penelusuran dengan pointer memajukan register alamat sebesar ukuran elemen setiap putaran. - Batas berhenti pointer adalah alamat satu langkah di luar elemen terakhir, dihitung dengan
leadari jumlah elemen dikalikan ukuran elemen. - Menyimpan hasil ke label di section
.bssmemungkinkan pemeriksaan nilai penuh lewat gdb denganx/1gxuntuk membandingkannya dengan keluaran program.
Referensi#
- NASM 2.16.03 documentation — NASM Project
— bab effective address menjadi dasar bentuk
[array + rsi*8]dan perhitungan konstantaequ. - Intel 64 and IA-32 Architectures Software Developer’s Manual — Intel — Volume 2 menjelaskan perhitungan alamat efektif yang melandasi kedua pola penelusuran.