Minggu 10 Pemrograman

Bab 10: Prosedur & Modul Program

Mempelajari cara memecah program menjadi modul-modul prosedur, memahami mekanisme stack, instruksi CALL/RET, dan teknik penerusan parameter pada Assembly 8086.

Capaian Pembelajaran Bab Ini

Setelah mempelajari bab ini, mahasiswa mampu menjelaskan organisasi dan mekanisme kerja stack; menulis dan memanggil prosedur menggunakan instruksi CALL dan RET; menerapkan konsep save/restore register; melakukan penerusan parameter melalui register, memori, dan stack; serta membandingkan prosedur dengan makro.

10.1 Mengapa Prosedur Diperlukan

Saat program semakin besar, menulis semua kode dalam satu blok monolitik menjadi sulit dibaca, sulit di-debug, dan sulit dimaintenance. Prosedur (disebut juga subroutine atau function) memungkinkan kita memecah program menjadi modul-modul kecil yang masing-masing bertanggung jawab atas satu tugas spesifik.

Reusability

Kode yang sama tidak perlu ditulis ulang — cukup panggil prosedurnya

Maintainability

Bug diperbaiki di satu tempat, otomatis berlaku untuk semua pemanggil

Modularity

Program besar dipecah menjadi modul-modul kecil yang mudah dipahami

Analogi: Resep Masakan

❌ Tanpa Prosedur

Resep nasi goreng menuliskan langkah menanak nasi dari awal (cuci beras, ukur air, nyalakan kompor, tunggu 15 menit...) di setiap resep yang butuh nasi. Padahal langkahnya sama.

✓ Dengan Prosedur

Buat satu prosedur "TanakNasi()" yang bisa dipanggil dari resep nasi goreng, nasi kuning, atau lontong. Jika ada cara lebih baik menanak nasi, ubah hanya di satu tempat.

Dalam bahasa C, prosedur disebut function. Dalam Assembly 8086, kita menggunakan instruksi CALL untuk memanggil dan RET untuk kembali. Namun sebelum memahami CALL/RET, kita harus memahami stack — karena stack adalah mekanisme yang membuat CALL/RET bekerja.

10.2 Konsep Stack (Tumpukan)

Stack adalah area memori khusus yang beroperasi dengan prinsip LIFO (Last In, First Out — masuk terakhir, keluar pertama). Bayangkan setumpuk piring: piring yang ditaruh terakhir akan diambil pertama.

Operasi PUSH (masukkan)
...
...
Data lama
← PUSH AX
← PUSH BX
↑ SP menunjuk ke sini
Operasi POP (ambil)
...
...
Data lama
AX (masih ada)
← POP BX
↑ SP pindah ke sini

Tiga Fakta Penting tentang Stack 8086

  • Tumbuh ke bawah (grows downward): Alamat stack menurun setiap kali data dimasukkan. SP dimulai dari alamat tinggi dan bergerak ke alamat rendah.
  • Selalu 16-bit (word): Setiap operasi PUSH/POP selalu menangani 2 byte, tidak peduli apakah data aslinya 8-bit atau 16-bit.
  • Berada di segmen stack (SS:SP): Lokasi fisik stack ditentukan oleh register SS (Stack Segment) dan SP (Stack Pointer).

10.3 Register SS dan SP

Dua register segment yang mengelola stack:

RegisterNamaFungsiUkuran
SS Stack Segment Menunjuk ke segmen memori tempat stack berada. Menentukan alamat dasar stack. 16 bit
SP Stack Pointer Menunjuk ke elemen teratas (top of stack). Berubah otomatis oleh PUSH, POP, CALL, RET, dan INT. 16 bit

Alamat fisik elemen teratas stack dihitung sebagai: SS × 16 + SP. Pada model memori .model small, SS biasanya sama dengan DS (segmen data dan stack berada di segmen yang sama). SP diinisialisasi oleh direktif .stack 100h yang mengalokasikan 256 byte dan menempatkan SP di awal area tersebut.

Segmen Stack (SS)
SS:FFFE SP awal (setelah .stack)
SS:FFFC
SS:FFFA ← PUSH pertama akan ke sini
SS:FFF8
...
SS:FF00 Batas bawah stack (256 byte)
Alamat tinggi di atas, alamat rendah di bawah. SP bergerak ke bawah saat PUSH.

10.4 PUSH dan POP

PUSH dan POP adalah dua instruksi dasar untuk mengoperasikan stack.

PUSH src

1. SP ← SP − 2

2. [SS:SP] ← src (16-bit)

PUSH AX     ; simpan AX ke stack
PUSH BX     ; simpan BX ke stack
PUSH DS     ; simpan DS ke stack
PUSH SI     ; simpan SI ke stack

POP dest

1. dest ← [SS:SP] (16-bit)

2. SP ← SP + 2

POP  SI     ; ambil dari stack ke SI
POP  DS     ; ambil dari stack ke DS
POP  BX     ; ambil dari stack ke BX
POP  AX     ; ambil dari stack ke AX

⚠ Urutan POP harus kebalikan PUSH! Jika Anda push AX, BX, CS — maka harus pop CS, BX, AX (LIFO). Jika urutan terbalik, nilai akan masuk ke register yang salah.

Apa yang Bisa Di-PUSH/POP?

BisaTidak Bisa
Register 16-bit: AX, BX, CX, DX, SI, DI, BP, SP Register 8-bit: AL, AH, BL, dll.
Segment register: CS, DS, SS, ES Immediate value: PUSH 5 ← ERROR
Memory operand 16-bit: PUSH [SI] Memory operand 8-bit: PUSH BYTE PTR [SI] ← ERROR
Flags: PUSHF / POPF

Catatan: Pada 8086, PUSH immediate tidak didukung. Fitur ini baru tersedia mulai 80186.

10.5 Ilustrasi Operasi Stack Langkah demi Langkah

Misalkan awalnya SP = 1000h, AX = 1234h, dan BX = 5678h. Perhatikan bagaimana stack berubah:

LangkahInstruksiSP[SS:SP][SS:SP+2]Keterangan
01000h????Keadaan awal
1PUSH AX0FFEh1234h??SP−2, AX disimpan di [0FFEh]
2PUSH BX0FFCh5678h1234hSP−2, BX disimpan di [0FFCh]
3POP CX0FFEh1234h??CX = 5678h (dari top), SP+2
4POP DX1000h????DX = 1234h (dari top), SP+2

Hasil Akhir

AX1234h
BX5678h
CX5678h
DX1234h

CX mendapat nilai BX dan DX mendapat nilai AX — inilah efek LIFO. SP kembali ke 1000h (seimbang).

10.6 Aturan Emas Stack

Melanggar aturan ini adalah sumber bug paling umum dalam pemrograman prosedur pada 8086:

1

Setiap PUSH harus memiliki POP yang sesuai

Jumlah PUSH dan POP di dalam satu prosedur harus seimbang. Jika tidak, SP akan salah saat RET.

2

Urutan POP harus kebalikan PUSH

PUSH AX, BX → POP BX, AX. Jika urutan salah, register akan mendapat nilai yang salah.

3

Jangan akses di bawah batas stack

Jika SP melewati batas bawah stack (stack underflow), data akan ditulis ke area memori yang tidak diperuntukkan bagi stack — bisa menimpa kode atau data penting.

4

Save register yang akan diubah, restore sebelum RET

Jika prosedur menggunakan AX, BX, CX — push ketiganya di awal, pop ketiganya (terbalik) sebelum RET. Dengan begitu register pemanggil tidak terpengaruh.

10.7 Stack Frame

Stack frame (juga disebut activation record) adalah blok data di stack yang terkait dengan satu pemanggilan prosedur. Stack frame berisi: alamat return, parameter, dan variabel lokal.

Alamat Tinggi ← pemanggil
Parameter ke-N [BP+8+]
Parameter 2 [BP+6]
Parameter 1 [BP+4]
Return Address (IP) [BP+2]
Old BP (disimpan BP lama) [BP+0] ← BP
Variabel lokal 1 [BP−2]
Variabel lokal 2 [BP−4]
Alamat Rendah ← SP

Gambar 10.1: Struktur stack frame tipikal. Parameter diakses via [BP+offset], variabel lokal via [BP−offset].

10.8 CALL dan RET

CALL dan RET adalah pasangan instruksi yang mengimplementasikan mekanisme pemanggilan prosedur.

CALL target

Near CALL:

1. PUSH IP (alamat instruksi berikutnya)

2. IP ← alamat target

Far CALL:

1. PUSH CS

2. PUSH IP

3. CS:IP ← alamat target

RET [n]

Near RET:

1. POP IP

Far RET:

1. POP IP

2. POP CS

RET n (membersihkan parameter):

1. POP IP

2. SP ← SP + n

Program Utama
...
CALL prosedur ← ①
instrksi_selanjut: ← ④
...
② PUSH IP
lalu lompat
④ POP IP
kembali
Prosedur
prosedur: ← ②
...
instruksi prosedur
...
RET ← ③
① CALL → ② Push return address & jump → ③ Eksekusi prosedur → ④ RET kembali ke instruksi setelah CALL

RET n — Membersihkan Parameter dari Stack

Jika parameter dikirim via stack, setelah RET alamat return sudah di-pop tapi parameter masih tersisa di stack. RET n secara otomatis menambah SP sebesar n setelah pop, sehingga parameter ikut terbuang. Misalnya, jika 2 parameter word dikirim: RET 4 (2 × 2 byte = 4).

10.9 Deklarasi Prosedur (PROC / ENDP)

Prosedur dideklarasikan menggunakan pasangan direktif PROC dan ENDP. Tipe prosedur (NEAR atau FAR) menentukan apakah CALL/RET hanya menyimpan IP atau CS dan IP.

Sintaks Prosedur
; Near procedure (default, segmen kode sama)
nama_prosedur PROC NEAR
    ; ... isi prosedur ...
    RET
nama_prosedur ENDP

; Far procedure (segmen kode berbeda)
nama_prosedur PROC FAR
    ; ... isi prosedur ...
    RET
nama_prosedur ENDP

NEAR vs FAR

AspekNEARFAR
Segmen kodeSama dengan pemanggilBisa berbeda dari pemanggil
CALL pushHanya IP (2 byte)CS + IP (4 byte)
RET popHanya IPIP + CS
Ukuran instruksi CALL3 byte5 byte
KecepatanLebih cepatLebih lambat
PenggunaanMayoritas prosedur (default)Prosedur antar segmen, ISR

Pada model .model small, hampir semua prosedur menggunakan NEAR (dan NEAR adalah default jika tidak ditulis).

10.10 Save & Restore Register

Prosedur yang "baik" tidak memiliki efek samping pada register yang digunakan oleh pemanggil. Caranya: simpan (push) register yang akan dimodifikasi di awal prosedur, dan kembalikan (pop) sebelum RET.

Pola Save/Restore
CETAK_KARAKTER PROC NEAR
    ; Input:  DL = karakter yang akan dicetak
    ; Output: tidak ada
    ; Mengubah: AX, DX (disimpan dan dikembalikan)

    PUSH AX              ; ① simpan register yang akan dipakai
    PUSH DX              ; ② urutan push: AX, DX

    MOV  AH, 02h         ; fungsi DOS: cetak karakter
    ; DL sudah berisi karakter dari pemanggil
    INT  21h

    POP  DX              ; ③ urutan pop: DX, AX (kebalikan!)
    POP  AX              ; ④ register dikembalikan ke nilai semula
    RET
CETAK_KARAKTER ENDP

Kode 10.1: Prosedur dengan save/restore register. Pemanggil bisa yakin AX dan DX tidak berubah setelah CALL.

Di mana menaruh PUSH/POP? Push segera setelah label prosedur (sebelum kode apapun). Pop tepat sebelum RET (setelah semua kode prosedur). Jangan push/pop di tengah-tengah logika prosedur karena mempersulit pembacaan dan rawan error.

10.11 Penerusan Parameter: Via Register

Metode paling sederhana: parameter disimpan di register sebelum CALL, dan prosedur membacanya dari register tersebut. Cocok untuk parameter sedikit (1–4 parameter).

Contoh: Prosedur CETAK_ANGKA — cetak angka 0-9
; Prosedur: CETAK_ANGKA
; Input:  AL = angka (0-9)
; Output: mencetak angka tersebut ke layar
; Mengubah: AX, DX (disave/direstore)

CETAK_ANGKA PROC NEAR
    PUSH AX
    PUSH DX

    ADD  AL, 30h          ; konversi angka 0-9 ke ASCII '0'-'9'
    MOV  DL, AL           ; DL = karakter ASCII
    MOV  AH, 02h          ; fungsi cetak karakter
    INT  21h

    POP  DX
    POP  AX
    RET
CETAK_ANGKA ENDP


; === Program Utama ===
    MOV  AL, 7             
    CALL  CETAK_ANGKA       ; mencetak '7' ke layar

    MOV  AL, 3             ; parameter: angka 3
    CALL  CETAK_ANGKA       ; mencetak '3' ke layar

Kode 10.2: Parameter via register — AL berisi angka yang akan dicetak.

Konvensi Parameter via Register (Umum)

RegisterPeran UmumContoh
AL / AXParameter pertama / nilai kembalianAL = karakter, AX = hasil penjumlahan
BXParameter kedua / pointer dataBX = offset string
CXCounter / panjang dataCX = jumlah elemen array
DXParameter ketiga / port I/ODX = alamat port
SI / DIPointer source / destinationSI = offset awal, DI = offset tujuan

10.12 Penerusan Parameter: Via Memori

Parameter disimpan di variabel global di segmen data. Prosedur mengaksesnya langsung menggunakan nama variabel. Metode ini paling mudah dipahami tapi kurang modular.

Contoh: Hitung luas persegi panjang
.data
    panjang dw 15             ; parameter 1 (global)
    lebar   dw 8              ; parameter 2 (global)
    luas    dw ?              ; hasil (global)

.code
; Prosedur: HITUNG_LUAS
; Membaca: panjang, lebar (variabel global)
; Menulis: luas (variabel global)

HITUNG_LUAS PROC NEAR
    PUSH AX
    PUSH BX

    MOV  AX, panjang      ; AX = panjang
    MOV  BX, lebar        ; BX = lebar
    MUL  BX               ; DX:AX = panjang × lebar
    MOV  luas, AX         ; simpan hasil (asumsi tidak overflow)

    POP  BX
    POP  AX
    RET
HITUNG_LUAS ENDP


; === Program Utama ===
    MOV  panjang, 15        ; set parameter
    MOV  lebar, 8          ; set parameter
    CALL  HITUNG_LUAS       ; hasil: luas = 120

Kode 10.3: Parameter via memori — prosedur membaca/menulis variabel global langsung.

Kelemahan Metode Ini: Prosedur terikat pada nama variabel tertentu (panjang, lebar, luas), sehingga tidak bisa di-reuse untuk menghitung luas dari pasangan nilai lain tanpa mengubah variabel global terlebih dahulu. Ini melanggar prinsip modularitas.

10.13 Penerusan Parameter: Via Stack

Metode paling fleksibel dan profesional. Parameter di-PUSH ke stack sebelum CALL, dan prosedur mengaksesnya via [BP + offset]. Ini juga memungkinkan rekursi.

Contoh: Jumlah dua bilangan via stack
; Prosedur: TAMBAH
; Parameter [BP+4]: bilangan pertama (word)
; Parameter [BP+6]: bilangan kedua   (word)
; Return: AX = hasil penjumlahan

TAMBAH PROC NEAR
    PUSH BP                ; ① simpan BP pemanggil
    MOV  BP, SP           ; ② BP = SP (BP sekarang menunjuk stack frame)

    MOV  AX, [BP+4]      ; ③ AX = parameter 1
    ADD  AX, [BP+6]      ; ④ AX += parameter 2

    POP  BP                ; ⑤ kembalikan BP pemanggil
    RET  4                ; ⑥ kembali & bersihkan 4 byte parameter
TAMBAH ENDP


; === Program Utama ===
    PUSH 25                ; push parameter 2 (kedua dulu!)
    PUSH 17                ; push parameter 1
    CALL  TAMBAH           ; AX = 17 + 25 = 42
    ; RET 4 di dalam prosedur sudah membersihkan stack

Kode 10.4: Parameter via stack dengan RET n untuk membersihkan parameter.

Isi Stack saat di dalam prosedur TAMBAH
Param 2: 25 (0019h) [BP+6]
Param 1: 17 (0011h) [BP+4]
Return Address (IP) [BP+2]
Old BP ← BP menunjuk sini [BP]

⚠ Urutan Push Parameter: Parameter yang di-push terakhir berada di alamat paling rendah di stack, sehingga menjadi parameter pertama ([BP+4]). Jadi jika konvensinya "param1, param2", maka di program utama: PUSH param2 dulu, lalu PUSH param1. Ini sesuai konvensi C (right-to-left push order).

10.14 Contoh Program: Prosedur Cetak String

Prosedur ini menerima offset string di register DX dan mencetak seluruh string hingga karakter '$'. Ini membungkus fungsionalitas INT 21h AH=09h ke dalam prosedur yang lebih rapi.

Assembly 8086
.model small
.stack 100h

.data
    pesan1  db 'Halo dari prosedur!$'
    pesan2  db 'Prosedur membuat kode rapi.$'
    newline db 0Dh, 0Ah, '$'  ; CR + LF

.code
; === PROSEDUR-PROSEDUR ===

; CETAK_STR: Cetak string yang diakhiri '$'
; Input:  DX = offset string
CETAK_STR PROC NEAR
    PUSH AX
    MOV  AH, 09h
    INT  21h
    POP  AX
    RET
CETAK_STR ENDP

; CETAK_NEWLINE: Pindah ke baris baru
CETAK_NEWLINE PROC NEAR
    PUSH DX
    LEA  DX, newline
    CALL  CETAK_STR        ; prosedur memanggil prosedur!
    POP  DX
    RET
CETAK_NEWLINE ENDP


; === PROGRAM UTAMA ===
mulai:
    MOV  AX, @data
    MOV  DS, AX

    LEA  DX, pesan1       ; parameter via register
    CALL  CETAK_STR

    CALL  CETAK_NEWLINE     ; pindah baris baru

    LEA  DX, pesan2
    CALL  CETAK_STR

    MOV  AH, 4Ch
    INT  21h

end mulai

Kode 10.5: Prosedur memanggil prosedur (nested call). CETAK_NEWLINE memanggil CETAK_STR di dalamnya.

Apa yang Terjadi di Stack Saat Nested Call?

MomenStack (top di atas)
CALL CETAK_STR (1x)[Return Addr 1] ← SP
RET dari CETAK_STR(kosong, SP kembali)
CALL CETAK_NEWLINE[Return Addr 2] ← SP
PUSH DX di dalamnya[DX lama] ← SP
[Return Addr 2]
CALL CETAK_STR (2x)[Return Addr 3] ← SP
[DX lama]
[Return Addr 2]
RET dari CETAK_STR[DX lama] ← SP
[Return Addr 2]
POP DX[Return Addr 2] ← SP
RET dari CETAK_NEWLINE(kosong, SP kembali)

Stack menangani nested call secara otomatis — setiap CALL push return address, setiap RET pop-nya. Tidak perlu konfigurasi tambahan.

10.15 Contoh Program: Prosedur Perkalian Repeated Addition

Karena 8086 tidak memiliki instruksi perkalian untuk operasi sederhana yang kita kendalikan penuh, kita bisa membuat prosedur perkalian menggunakan penjumlahan berulang: a × b = a + a + ... + a (b kali).

Assembly 8086
; Prosedur: KALI
; Input:  AX = pengali (a), BX = pengali (b)
; Output: AX = a × b
; Catatan: hanya untuk bilangan positif kecil

KALI PROC NEAR
    PUSH CX
    PUSH DX

    MOV  CX, BX           ; CX = b (banyaknya penjumlahan)
    MOV  DX, 0             ; DX = akumulator hasil
    JCXZ kali_selesai      ; jika b = 0, hasil = 0

kali_ulang:
    ADD  DX, AX           ; DX += a
    LOOP kali_ulang         

kali_selesai:
    MOV  AX, DX           ; AX = hasil

    POP  DX
    POP  CX
    RET
KALI ENDP


; === Pemanggilan ===
    MOV  AX, 7             ; a = 7
    MOV  BX, 6             ; b = 6
    CALL  KALI             ; AX = 42

Kode 10.6: Perkalian dengan repeated addition — mendemonstrasikan parameter via register (AX, BX) dan return value via register (AX).

10.16 Contoh Program: Rekursi — Faktorial

Rekursi adalah prosedur yang memanggil dirinya sendiri. Stack sangat penting di sini karena setiap pemanggilan rekursif membuat stack frame baru. Berikut perhitungan faktorial: n! = n × (n−1)!.

Assembly 8086 — Faktorial Rekursif
; Prosedur: FAKTORIAL
; Input:  AX = n (bilangan bulat ≥ 0)
; Output: AX = n!
; Mengubah: BX, CX (disave/direstore)

FAKTORIAL PROC NEAR
    PUSH BX
    PUSH CX

    CMP  AX, 1             ; base case: n ≤ 1?
    JBE   fakt_base        ; ya, 1! = 1 dan 0! = 1

    ; Recursive case: n! = n × (n-1)!
    MOV  CX, AX           ; CX = n (simpan untuk dikalikan nanti)
    DEC  AX                ; AX = n - 1
    PUSH CX                ; simpan n di stack (aman dari CALL)
    CALL  FAKTORIAL         ; AX = (n-1)!
    POP  CX                ; CX = n (ambil kembali)
    MUL  CX                ; DX:AX = (n-1)! × n
    JMP  fakt_selesai

fakt_base:
    MOV  AX, 1             ; 0! = 1! = 1

fakt_selesai:
    POP  CX
    POP  BX
    RET
FAKTORIAL ENDP


; === Pemanggilan: hitung 5! ===
    MOV  AX, 5             ; n = 5
    CALL  FAKTORIAL         ; AX = 120

Kode 10.7: Faktorial rekursif — setiap CALL FAKTORIAL menambah stack frame baru ke stack.

Trace Rekursi: FAKTORIAL(5)

FAKTORIAL(5) → simpan CX=5, panggil FAKTORIAL(4)
FAKTORIAL(4) → simpan CX=4, panggil FAKTORIAL(3)
FAKTORIAL(3) → simpan CX=3, panggil FAKTORIAL(2)
FAKTORIAL(2) → simpan CX=2, panggil FAKTORIAL(1)
FAKTORIAL(1) → base case, return AX=1
FAKTORIAL(2) → AX = 1 × 2 = 2, return
FAKTORIAL(3) → AX = 2 × 3 = 6, return
FAKTORIAL(4) → AX = 6 × 4 = 24, return
FAKTORIAL(5) → AX = 24 × 5 = 120, return

Pada titik terdalam, stack berisi 5 stack frame (5× return address + 5× CX + 5× BX + 5× CX internal). Total sekitar 40 byte stack untuk n=5. Untuk n=10, stack frame 10× lebih banyak.

⚠ Bahaya Rekursi pada 8086: Stack 8086 sangat terbatas (default hanya 256 byte dengan .stack 100h). Rekursi dalam mendalam dapat menyebabkan stack overflow. Untuk faktorial, n > ~30 sudah berbahaya. Selalu alokasikan stack yang cukup besar untuk program rekursif.

10.17 Prosedur vs Makro

Meskipun keduanya digunakan untuk modularisasi kode, prosedur dan makro bekerja dengan cara yang sangat berbeda. Pemilihan yang tepat tergantung pada konteks penggunaan.

AspekProsedur (CALL/RET)Makro
Waktu ekspansi Run-time — kode dipanggil saat eksekusi Assemble-time — kode disalin oleh assembler
Salinan kode di memori 1 salinan — hemat memori N salinan — setiap pemanggilan = 1 salinan baru
Overhead eksekusi Ada — PUSH return addr, jump, POP, jump back Tidak ada — kode inline, secepat kode biasa
Ukuran memori kode Kecil (kode prosedur + 3 byte per CALL) Besar (kode makro × jumlah pemanggilan)
Parameter Via register, memori, atau stack Via argumen makro (substitusi teks)
Rekursi Mendukung (stack frame) Tidak mendukung (akan ekspansi tak terbatas)
Debugging Lebih mudah (satu lokasi kode) Lebih sulit (kode tersebar di banyak tempat)
Direktif PROC / ENDP MACRO / ENDM
Contoh Deklarasi Makro
; Deklarasi makro: CETAK_CHAR
CETAK_CHAR MACRO karakter
    PUSH AX
    PUSH DX
    MOV  DL, karakter
    MOV  AH, 02h
    INT  21h
    POP  DX
    POP  AX
ENDM

; Pemanggilan makro (diassembler menjadi kode inline)
    CETAK_CHAR 'A'           ; assembler ganti dengan isi makro, DL='A'
    CETAK_CHAR 'B'           ; assembler ganti dengan salinan lain, DL='B'
    CETAK_CHAR 'C'           ; assembler ganti dengan salinan lain, DL='C'

; Setelah assemble, hasilnya sama seperti menulis:
;   PUSH AX / PUSH DX / MOV DL,'A' / MOV AH,02h / INT 21h / POP DX / POP AX
;   PUSH AX / PUSH DX / MOV DL,'B' / MOV AH,02h / INT 21h / POP DX / POP AX
;   PUSH AX / PUSH DX / MOV DL,'C' / MOV AH,02h / INT 21h / POP DX / POP AX

Kode 10.8: Deklarasi dan pemanggilan makro. Perhatikan bahwa tidak ada CALL/RET — kode di-inline langsung.

Gunakan Prosedur Jika...

  • • Kode makro panjang (>10 instruksi)
  • • Dipanggil dari banyak tempat
  • • Butuh rekursi
  • • Memori kode terbatas
  • • Butuh modularitas sejati

Gunakan Makro Jika...

  • • Kode sangat pendek (1–5 instruksi)
  • • Dipanggil sedikit kali (1–3x)
  • • Performa sangat kritis
  • • Butuh fleksibilitas parameter tipe apapun
  • • Memori kode berlimpah

10.18 Soal Latihan

Kerjakan soal-soal berikut untuk menguji pemahaman Anda. Klik "Lihat jawaban" setelah mencoba menjawab sendiri.

Soal 1. Jelaskan perbedaan antara prosedur (near call) dan makro. Sebutkan masing-masing satu keuntungan dan satu kerugian.

Lihat jawaban

Prosedur adalah blok kode yang ada satu kali di memori dan dipanggil via CALL/RET pada saat run-time. Keuntungan: menghemat memori kode. Kerugian: ada overhead CALL/RET (push return address, jump, pop, jump back).

Makro adalah template yang diekspansi oleh assembler pada saat assemble-time — setiap pemanggilan disalin ulang. Keuntungan: tidak ada overhead eksekusi (secepat kode inline). Kerugian: memori kode membengkak jika makro dipanggil berkali-kali dan berisi banyak instruksi.

Soal 2. Apa yang terjadi di dalam stack saat instruksi CALL prosedur dieksekusi? Jelaskan langkah demi langkah.

Lihat jawaban

Saat CALL prosedur dieksekusi: (1) CPU menghitung alamat instruksi berikutnya (alamat return = IP + panjang instruksi CALL), (2) alamat return tersebut di-PUSH ke stack (SP dikurangi 2, lalu alamat disimpan di [SS:SP]), (3) IP diisi dengan alamat tujuan prosedur, sehingga eksekusi berpindah ke sana. Saat RET: (1) alamat return di-POP dari stack (nilai di [SS:SP] dipindah ke IP, SP ditambah 2), (2) eksekusi kembali ke instruksi setelah CALL.

Soal 3. Mengapa aturan "PUSH dan POP harus seimbang" sangat penting dalam prosedur? Apa yang terjadi jika melanggarnya?

Lihat jawaban

Jika di dalam prosedur Anda melakukan PUSH tapi tidak POP (atau sebaliknya), nilai SP akan tidak kembali ke semula saat RET. Akibatnya, RET akan mengambil alamat return yang salah dari stack, dan program akan melompat ke alamat yang tidak terduga — biasanya menyebabkan crash atau perilaku tak terdefinisi. Aturan emas: setiap PUSH di dalam prosedur harus memiliki POP yang sesuai sebelum RET.

Soal 4. Bandingkan tiga metode penerusan parameter (via register, via memori, via stack). Kapan masing-masing paling tepat digunakan?

Lihat jawaban

Via Register: Parameter disimpan di register (AX, BX, dll.). Paling cepat, kode paling ringkas. Cocok untuk parameter sedikit (1-4 parameter) dan tipe data sederhana.

Via Memori: Parameter disimpan di variabel global di segmen data. Mudah dipahami oleh pemula. Cocok untuk parameter banyak atau data kompleks (array, string). Kerugian: menggunakan memori global sehingga prosedur tidak sepenuhnya modular.

Via Stack: Parameter di-PUSH ke stack sebelum CALL, diakses via [BP+offset] di dalam prosedur. Paling fleksibel dan mendukung rekursi. Cocok untuk prosedur library yang benar-benar modular. Kerugian: kode lebih kompleks dan sedikit lebih lambat karena akses memori via BP.

Soal 5. Apa fungsi register BP (Base Pointer) dalam prosedur yang menerima parameter via stack? Mengapa tidak menggunakan SP langsung?

Lihat jawaban

BP digunakan sebagai frame pointer tetap untuk mengakses parameter dan variabel lokal di stack. Alasan tidak menggunakan SP langsung: SP berubah-ubah selama eksekusi prosedur (setiap PUSH/POP mengubah SP), sehingga offset relatif terhadap SP tidak konsisten. BP diset sekali di awal prosedur (MOV BP, SP) dan tidak diubah lagi, sehingga parameter selalu bisa diakses pada offset yang tetap (misalnya [BP+4], [BP+6]).

Soal 6. Tulis prosedur Assembly 8086 bernama TITIK yang mencetak satu karakter titik (.) ke layar menggunakan INT 21h, lalu panggil prosedur tersebut 5 kali dari program utama untuk mencetak ".....".

Lihat jawaban

Kode:
TITIK PROC
PUSH AX
PUSH DX
MOV DL, '.'
MOV AH, 02h
INT 21h
POP DX
POP AX
RET
TITIK ENDP

; Program utama:
MOV CX, 5
cetak_lima:
CALL TITIK
LOOP cetak_lima


Setiap pemanggilan CALL TITIK akan mencetak satu titik. LOOP memanggilnya 5 kali, menghasilkan ".....".

10.19 Kuis Bab 10

Uji pemahaman Anda. Pilih satu jawaban yang paling tepat. Setelah memilih, jawaban benar akan ditandai dan penjelasan muncul.

1. Instruksi apa yang secara otomatis menyimpan alamat return ke stack?

2. Setelah eksekusi PUSH AX, nilai SP akan...

3. Apa perbedaan antara CALL dan CALL FAR?

4. Dalam prosedur yang menerima parameter via stack, mengapa BP digunakan alih-alih SP?

5. Makro diekspansi pada saat kapan?

6. Berapa byte yang ditempati di stack oleh satu kali PUSH pada 8086?

7. Jika SP = 1000h sebelum CALL, berapa nilai SP sesaat setelah CALL dieksekusi (near call)?

8. Keuntungan utama menggunakan prosedur dibandingkan menulis ulang kode yang sama adalah...