Bölüm 8: Prosedürler
Giriş
Öğrenme Çıktıları
PROC / ENDP ile prosedür tanımlamak; direktif ile işlemci komutunu ayırt etmek.
Dönüş adresinin yığına nasıl itildiğini ve RET ile nasıl geri alındığını SP ve IP değerleriyle izlemek.
Yazmaçlar, bellek değişkenleri ve yığınla (BP çerçevesi) prosedüre veri gönderip sonuç almak.
Yazmaçları PUSH/POP ile korumak, yığını dengeli tutmak ve tipik hatalardan kaçınmak.
Giriş
Prosedür Nedir?
- Prosedür, belirli bir görevi gerçekleştiren kod parçasıdır (yüksek seviyeli dillerdeki fonksiyon / alt program).
- Programın içinden
CALLile çağrılır. - Görevi tamamlayınca
RETile çağrıldığı noktanın hemen arkasına geri döner. - Aynı kod tek kez yazılır, birçok yerden çağrılır.
- Programı daha yapılandırılmış (structured) ve anlaşılır hâle getirir.
Tanımlama
Prosedür Tanımlama
ad PROC ; başlangıç (varsayılan NEAR) ; prosedür kodu buraya yazılır RET ad ENDP ; bitiş
ad: prosedürün adıdır; başta ve sonda aynı olmalıdır.- Ad, etiket kurallarına uyar (rakamla başlayamaz).
- Prosedür içinde en az bir
RETbulunmalıdır.
Prosedür nereye yazılır?
ORG 100h
CALL p1
RET ; ana program biter
p1 PROC ; RET'ten sonra:
; ... ; sırayla yürütülmez
RET
p1 ENDP
ENDProsedürler ana programın RET'inden sonra konursa yanlışlıkla "düşerek" yürütülmez. Önce yazılacaksa üzerinden JMP ile atlanmalıdır.
Tanımlama
Direktifler ve Komutlar
| Öğe | Türü | Görevi |
|---|---|---|
| PROC / ENDP | Assembler direktifi | Prosedürün başlangıç adresini ve türünü (NEAR/FAR) assembler'a bildirir. Makine koduna çevrilmez, bellekte yer kaplamaz. |
| CALL | İşlemci komutu | Dönüş adresini yığına iter, prosedürün adresine atlar. |
| RET | İşlemci komutu | Dönüş adresini yığından alır (POP) ve oraya atlar. Hem prosedürden hem .COM programından dönmek için kullanılır. |
| END | Assembler direktifi | Kaynak dosyanın sonunu bildirir. |
INT 20h bulunur ve program sonlanır.Tanımlama
Modüler Programlama
; ana.asm EXTRN yaz:NEAR ; başka dosyada ... CALL yaz ; araclar.asm PUBLIC yaz ; dışarıya açık yaz PROC NEAR ... RET yaz ENDP
- Büyük programlar prosedürlere, prosedürler de ayrı kaynak dosyalarına (modüllere) bölünür.
PUBLICbir prosedür adını başka modüllere açar;EXTRNadın başka bir modülde tanımlı olduğunu ve türünü (NEAR/FAR) bildirir.- Her modül ayrı derlenir ve bir nesne dosyası (.OBJ) olur; bağlayıcı bunları birleştirir ve CALL komutlarındaki adresleri tamamlar.
- Sık kullanılan nesne dosyaları bir kütüphane (.LIB) dosyasında toplanır; bağlayıcı yalnızca gereken prosedürleri programa alır.
- emu8086'da ortak kod genellikle
INCLUDEile eklenir: dosyanın metni kaynağa katılır ve tek parça derlenir (emu8086.inc).
CALL ve RET
CALL ve RET Adım Adım
Başlangıç: IP = 0100h, SP = FFFEh. Sıradaki komut CALL m1 (3 bayt: E8 04 00).
CALL, önce dönüş adresini (sonraki komut: 0103h) yığına iter: SP = FFFCh, [FFFCh] = 0103h.
Sonra IP ← 0103h + 0004h = 0107h: prosedürün ilk komutu.
MOV BX, 5 yürütüldü, IP = 010Ah. Yığın değişmedi.
RET: IP ← [SP] = 0103h, SP = FFFEh. Akış, CALL'ın hemen arkasından sürer.
MOV AX, 2 → IP = 0106h. Buradaki RET 0000h'yı alır ve programı sonlandırır.
CALL ve RET
CALL / RET Türleri
| Komut | Kod | Bayt | Yığına / yığından | Açıklama |
|---|---|---|---|---|
| CALL near | E8 rel16 | 3 | IP | Aynı kesimde doğrudan çağrı (varsayılan). |
| CALL reg/mem | FF /2 | 2–4 | IP | Dolaylı yakın çağrı: CALL BX, CALL [tablo+SI]. |
| CALL far | 9A ofs seg | 5 | CS, sonra IP | Başka kesimdeki prosedür; CS:IP birlikte değişir. |
| RET | C3 | 1 | IP | Yakın dönüş. |
| RETF | CB | 1 | IP, sonra CS | Uzak dönüş (FAR prosedür). |
| RET n | C2 n16 | 3 | IP, SP += n | Dönüşte yığındaki n baytlık parametreyi de atar. |
ad PROC NEAR (varsayılan) ya da ad PROC FAR yazıldığında assembler, prosedür içindeki RET'i uygun biçimde (C3 / CB) kodlar. Tek kesimli .COM örneklerinde prosedürler NEAR'dır.CALL ve RET
Uzak Prosedür: Yığın Düzeni
- Uzak (FAR) prosedür başka bir kod kesimindedir; ona ulaşmak için hem CS hem IP değişmelidir.
CALL FARönce CS'yi, sonra IP'yi iter: yığında 4 bayt.RETFönce IP'yi, sonra CS'yi çeker.- Yakın
CALLyalnızca IP'yi (2 bayt) iter,RETyalnızca IP'yi çeker. - Prosedürün türü ile dönüş komutu uyuşmalıdır:
PROC FARyazıldığında assembler içteki RET'i RETF (CB) olarak kodlar. - .COM programı tek kesimde çalıştığı için yakın çağrı yeterlidir; birden çok kod kesimi olan .EXE programlarında uzak çağrı gerekir.
CALL ve RET · Kodu adım adım çalıştır
Kod İzleme: PUSH CS + CALL ile RETF
AX = 5. Bu kod, CALL FAR'ın yığına yaptığı iki itmeyi iki ayrı komutla taklit eder.
PUSH CS: kod kesimi (bu çalıştırmada 0700h) yığına itilir; uzak çağrının ilk yarısı.
Yakın CALL uzak dönüş adresini (0107h) iter ve 010Bh'ye atlar. Yığın artık uzak çağrının düzenindedir: tepede IP, altında CS.
SHL AX, 1: AX = 000Ah.
RETF iki sözcük çeker: IP = 0107h, CS = 0700h; SP 4 artarak FFFEh olur.
Akış ADD AX, 1'den sürer: AX = 000Bh. Gerçek CALL FAR (9A, 5 bayt) aynı iki itmeyi tek komutla yapar.
ORG 100h MOV AX, 5 PUSH CS ; uzak çağrının ilk yarısı CALL uzak ; IP itilir ADD AX, 1 RET uzak PROC SHL AX, 1 RETF ; önce IP, sonra CS uzak ENDP
CALL ve RET · Kodu adım adım çalıştır
Kod İzleme: Tablo ve Yazmaçla Dolaylı CALL
AX = 6. tablo, iki prosedürün adresini tutan bir sözcük dizisidir (0114h, 0116h).
SI = 2: tablodaki 2. eleman (her eleman 2 bayt).
CALL tablo[SI]: hedef adres bellekteki [tablo+2] = 0116h'den okunur. Dönüş adresi 010Ah yığına, IP = 0116h.
iki_kat: SHL AX, 1 → AX = 12.
RET: IP = 010Ah, ana programa dönüldü.
BX = OFFSET bir_ekle = 0114h.
CALL BX: hedef yazmaçtan alınır. Dönüş 010Fh yığına, IP = 0114h.
bir_ekle: AX = 13.
RET → 010Fh. Hangi prosedürün çağrılacağı çalışma anında SI ya da BX ile seçildi.
MOV AX, 6 MOV SI, 2 ; 2. işlem (× 2) CALL tablo[SI] ; bellekle dolaylı MOV BX, OFFSET bir_ekle CALL BX ; yazmaçla dolaylı RET tablo DW bir_ekle, iki_kat bir_ekle: INC AX RET iki_kat: SHL AX, 1 RET
CALL ve RET
Atlama Tablosuyla Menü
MOV AH, 1 INT 21h ; AL = '1'..'3' SUB AL, '1' ; 0..2 CMP AL, 2 JA gecersiz ; aralık denetimi MOV BL, AL MOV BH, 0 SHL BX, 1 ; her adres 2 bayt CALL islem[BX] gecersiz: RET islem DW topla, cikar, carp
- Menüdeki her seçenek bir prosedürdür; adresleri
islemtablosunda sırayla durur. - Tuş, tablo indisine çevrilir: '1' → 0, '2' → 1, '3' → 2. Adresler 2 bayt olduğundan indis 2 ile çarpılır (
SHL BX, 1). JAişaretsiz karşılaştırır: '0' tuşu 0 − 1 = FFh verir ve 2'den büyük sayıldığı için o da reddedilir. Tek denetim iki yönü de kapatır.- Aralık denetimi olmasaydı geçersiz bir tuş tablonun dışındaki baytları adres sanıp oraya çağrı yapardı.
| Tuş | BX | Çağrılan |
|---|---|---|
| 1 | 0 | topla |
| 2 | 2 | cikar |
| 3 | 4 | carp |
| 0 · 9 | — | hiçbiri (RET) |
CALL ve RET · Kodu adım adım çalıştır
Kod İzleme: Basit Prosedür
Program 0100h'dan başlar. ORG, PROC, ENDP, END hiç bayt üretmez.
CALL m1: 0103h yığına itilir, IP = 0107h.
BX = 0005h.
RET: 0103h yığından alınır → IP = 0103h, SP = FFFEh.
AX = 0002h.
RET: 0000h alınır → IP = 0000h, SP = FFFEh + 2 = 0000h. PSP'deki INT 20h programı bitirir. Sonuç: AX = 2, BX = 5.
ORG 100h CALL m1 ; 0100h: E8 04 00 MOV AX, 2 ; 0103h: B8 02 00 RET ; 0106h: C3 → DOS m1 PROC MOV BX, 5 ; 0107h: BB 05 00 RET ; 010Ah: C3 → çağırana m1 ENDP END
CALL ve RET
Bellekteki Makine Kodu
- Örnek program bellekte yalnızca 11 bayt tutar:
E8 04 00 B8 02 00 C3 BB 05 00 C3. CALL m1hedefi mutlak adres olarak değil, sonraki komuta göre uzaklık (rel16) olarak saklar. Bu yüzden kod bellekte başka bir yere taşınsa da çalışır.- 16 bitlik değerler bellekte little-endian (little-endian) saklanır: önce düşük bayt (04), sonra yüksek bayt (00).
- Prosedürün adı (
m1) yalnızca assembler için vardır; makine kodunda yalnızca 0107h adresi kalır.
CALL ve RET
CALL ve RET'in Süresi
| Komut (8086) | Saat turu |
|---|---|
| CALL yakın | 19 |
| CALL yazmaç | 16 |
| CALL FAR | 28 |
| RET | 8 |
| RETF | 18 |
| PUSH yazmaç | 11 |
| POP yazmaç | 8 |
Çağrı yükü hesabı
- İki yazmaç saklayan bir prosedürün işi dışındaki yükü: 19 + 8 + 2 × (11 + 8) = 65 tur.
- 5 MHz'lik 8086'da bir tur 200 ns: 65 × 200 ns = 13 µs.
- Prosedür 1000 kez çağrılırsa yük 13 ms olur.
- Makro bu yükü taşımaz ama her kullanımda gövdeyi kopyalar.
Parametreler
Parametre Geçirme Yolları
number1, result gibi ortak değişkenleri okur/yazar. Kolay ama prosedür o değişkenlere bağımlı kalır.PUSH ile itilir, prosedür BP ile okur, RET n ile temizlenir. Yüksek seviyeli dillerin yöntemi.Parametreler
Değerle mi, Adresle mi?
Prosedüre verinin kopyası verilir (AX = 5). Prosedür kopyayı değiştirse de bellekteki asıl değişken değişmez. Tek sayı gibi küçük veriler için hızlı ve güvenlidir.
Verinin adresi verilir (SI = OFFSET a). Prosedür [SI] üzerinden asıl değişkeni okur ve değiştirebilir. Diziler, dizgiler ve prosedürün değiştirmesi gereken değişkenler için.
MOV AX, a ; değer: AX'te a'nın kopyası CALL iki_kat ; AX iki katına çıkar, a aynı kalır LEA SI, a ; adres: SI = OFFSET a CALL iki_kat_m ; [SI] iki katına çıkar, a değişir
LEA SI, a ile MOV SI, OFFSET a aynı sonucu verir: a'nın adresi. MOV SI, a ise adresi değil, a'nın içindeki değeri yükler; adresle geçirmede sık yapılan bir karışıklıktır.Parametreler · Kodu adım adım çalıştır
Kod İzleme: Adresle Takas
SI ve DI, a ve b değişkenlerinin adreslerini taşır (0115h, 0117h): prosedüre değerler değil, yerler gönderilir.
CALL takas: dönüş adresi 010Bh yığına, SP = FFFCh.
takas AX'i kullanacak: önce saklar (SP = FFFAh).
MOV AX, [SI]: a'nın değeri (1234h) okunur.
XCHG AX, [DI]: AX ile b tek komutta yer değiştirir: b = 1234h, AX = 00FFh.
MOV [SI], AX: a = 00FFh. Takas bellekte tamamlandı.
POP AX ve RET: AX eski değerine (0000h) döner, SP = FFFEh; a ile b'nin değişmiş hâli kalır.
ORG 100h LEA SI, a LEA DI, b CALL takas RET takas PROC ; Girdi: SI, DI (adres) PUSH AX MOV AX, [SI] XCHG AX, [DI] MOV [SI], AX POP AX RET takas ENDP a DW 1234h b DW 00FFh
Parametreler
Yazmaçlarla Parametre
- Örnekte AL ve BL yazmaçları iki parametreyi temsil eder.
m2prosedürü AL ve BL'yi çarpar, sonucu AX yazmacına koyar.CALL m2prosedürü çağırır;RETçağrıyı yapan yere döner.- m2 iki kez çağrılıyor: ikinci çağrıda AL, birinci çağrının sonucunu taşır, çünkü MUL AX'i (dolayısıyla AL'yi) değiştirir.
| MUL kaynak | İşlem | Sonuç |
|---|---|---|
| 8 bit | AL × kaynak | AX |
| 16 bit | AX × kaynak | DX:AX |
MUL işaretsiz çarpar; sonucun üst yarısı (AH ya da DX) 0 değilse CF = OF = 1 olur. Kaynak sabit olamaz: MUL 2 geçersizdir.
Parametreler · Kodu adım adım çalıştır
Kod İzleme: Yazmaçlarla Parametre
AL = 1 (MOV AL, 1 = B0 01, 2 bayt).
BL = 2.
1. çağrı: dönüş adresi 0107h itilir, IP = 010Bh.
MUL BL: AX = 1 × 2 = 0002h. AH = 0 → CF = OF = 0.
RET → IP = 0107h: ikinci CALL.
2. çağrı: bu kez dönüş adresi 010Ah itilir. Aynı prosedür, farklı dönüş noktası.
MUL BL: AL artık 2 → AX = 2 × 2 = 0004h.
RET → IP = 010Ah.
RET: DOS'a dönülür. Sonuç AX = 0004h.
ORG 100h MOV AL, 1 ; 1. parametre MOV BL, 2 ; 2. parametre CALL m2 ; 0104h: E8 04 00 CALL m2 ; 0107h: E8 01 00 RET ; 010Ah: DOS'a dön m2 PROC ; 010Bh MUL BL ; AX = AL * BL RET ; çağırana dön m2 ENDP END
Parametreler · Kodu adım adım çalıştır
Kod İzleme: Sonucu CF ile Döndürmek
rakam prosedürü AL'deki karakteri sayıya çevirir; başarıyı CF ile bildirir (DOS hizmetleri de böyle yapar). AL = '7' = 37h.
CALL rakam; SUB AL, '0': 37h − 30h = 7.
CMP AL, 10: 7 − 10 ödünç ister → CF = 1 (değer 0…9 aralığında).
CMC: CF = 0 → “rakamdır”.
RET; çağıran CF'ye bakabilir (JC/JNC). BL = 7 saklandı.
AL = 'x' = 78h; ikinci çağrı: 78h − 30h = 48h (72).
CMP AL, 10: 72 ≥ 10, ödünç yok → CF = 0.
CMC: CF = 1 → “rakam değil”. AL bozuldu (48h); bu durumda AL'nin değeri kullanılmamalıdır.
RET: sonuç bayrakta. Çağıran JC hata ile ayırt edebilir.
MOV AL, '7' CALL rakam ; CF = 0 → AL = değer MOV BL, AL MOV AL, 'x' CALL rakam ; CF = 1 → rakam değil RET rakam PROC ; Girdi AL, Çıktı CF SUB AL, '0' CMP AL, 10 ; 0..9 → ödünç, CF = 1 CMC ; rakamsa CF = 0 RET rakam ENDP
Parametreler
Birden Çok Sonuç Döndürmek
bolkalan PROC ; Girdi: AX, BL (bölen) CMP BL, 0 ; Çıktı: AL = bölüm JE hata ; AH = kalan DIV BL ; CF = 1: bölen 0 CLC RET hata: STC RET bolkalan ENDP
- Yazmaç çifti: 32 bitlik sonuç DX:AX'te (16 bitlik MUL gibi), bölüm ile kalan AL ve AH'de.
- Bayrak + yazmaç: CF başarıyı, yazmaç değeri bildirir; çağıran önce
JCile bakar. - Bellek: sonuçlar adresi verilen bir alana yazılır; sayıları sınırsızdır.
| Girdi | Sonuç |
|---|---|
| AX = 100, BL = 7 | AL = 0Eh (14), AH = 02h, CF = 0 |
| AX = 100, BL = 0 | CF = 1, AX değişmez (0064h) |
| AX = 1000, BL = 2 | bölüm 500 AL'ye sığmaz: bölme hatası (INT 0) |
Parametreler
Yazmaçların Korunması
Sorun
Prosedür, çağıranın kullandığı bir yazmacı habersizce değiştirirse (ör. döngü sayacı CX), çağıran kod bozulur.
Belgeleme
; m2: AL * BL hesaplar ; Girdi : AL, BL (işaretsiz) ; Çıktı : AX = AL * BL ; Bozar : CF, OF
Çözüm: PUSH / POP
yaz PROC PUSH AX ; kullanacaklarını PUSH SI ; sakla ; ... AX ve SI kullanılır POP SI ; TERS sırayla POP AX ; geri yükle RET yaz ENDP
Sonuç döndüren yazmaç (ör. AX) korunmaz; aksi hâlde sonuç kaybolur.
Parametreler · Kodu adım adım çalıştır
Kod İzleme: PUSH CX ile Sayaç Koruma
Ana döngü 2 satır yazacak: sayaç CX = 2. yildiz prosedürü de iç döngüsünde CX kullanıyor.
CALL yildiz: dönüş adresi 0106h yığına, SP = FFFCh.
PUSH CX: çağıranın sayacı (2) yığına saklandı.
İç sayaç CX = 3; AX = 0E23h (AH = 0Eh teletype, AL = '#').
İç döngü üç kez INT 10h: ###. LOOP sonunda CX = 0.
POP CX: çağıranın sayacı geri geldi, CX = 2.
RET: 0106h'ye dönülür, SP = FFFEh.
Ana LOOP satir: CX = 1 → ikinci satır için yeniden çağrı.
İkinci çağrı aynı şekilde: ### yazılır, CX = 1 geri yüklenir.
LOOP: CX = 0 → döngü biter. Ekranda 6 karakter. PUSH/POP olmasaydı dönüşte CX = 0 olur, LOOP onu FFFFh yapıp neredeyse sonsuz dönerdi.
MOV CX, 2 ; ana döngü: 2 satır satir: CALL yildiz LOOP satir RET yildiz PROC PUSH CX ; çağıranın sayacı MOV CX, 3 ; iç sayaç MOV AX, 0E23h ; AH = 0Eh, AL = '#' ic: INT 10h LOOP ic POP CX ; sayaç geri RET yildiz ENDP
Parametreler
Çağıran mı Korur, Çağrılan mı?
| Ölçüt | Çağrılan korur (prosedürde PUSH/POP) | Çağıran korur (CALL'ın çevresinde) |
|---|---|---|
| Kod nerede? | prosedürde bir kez | her çağrı noktasında |
| Gereksiz saklama | çağıran o yazmacı kullanmasa da saklanır | yalnızca gerçekten gereken yazmaçlar |
| Unutma riski | düşük: tek yerde yazılır | yüksek: her çağrıda tekrar gerekir |
| Uygun olduğu durum | kütüphane prosedürleri | çıktı yazmacındaki eski değerin gerektiği yer |
dongu: PUSH CX ; çağıran: sayaç CALL sayi_oku ; sonuç CX'te ADD toplam, CX POP CX ; sayaç geri LOOP dongu
Parametreler · Kodu adım adım çalıştır
Kod İzleme: Tek Çıkış Noktası
bul prosedürü SI'daki diziden CX eleman içinde AL'yi (7) arar; sonucu CF ve BX ile döndürür.
CALL bul: dönüş adresi 010Ch yığına.
Prosedür CX ve SI'yı değiştireceği için ikisini de saklar; SP = FFF8h. BX = 0 (indis).
dizi[0] = 3 ≠ 7: SI ve BX ilerler, LOOP CX'i 4'e indirir.
dizi[1] = 9 ≠ 7: BX = 2, CX = 3.
dizi[2] = 7: ZF = 1, JE buldu atlar. Döngü erken bitiyor ama yığında hâlâ iki saklı değer var.
STC: CF = 1 → “bulundu”. Bulunamasaydı CLC ve JMP son aynı yere gelirdi.
Her iki yol da son etiketinde birleşir: POP'lar her durumda çalışır, SI ve CX eski değerlerine döner.
RET doğru adrese (010Ch) döner: CF = 1, BX = 2. JE'nin yerinde doğrudan RET olsaydı tepedeki SI değeri (0120h) dönüş adresi sanılırdı.
ORG 100h LEA SI, dizi MOV CX, 5 MOV AL, 7 CALL bul ; CF = 1: bulundu RET bul PROC ; Girdi: SI, CX, AL PUSH CX PUSH SI XOR BX, BX ; Çıktı: BX = indis ara: CMP [SI], AL JE buldu INC SI INC BX LOOP ara CLC ; bulunamadı JMP son buldu: STC son: POP SI ; tek çıkış noktası POP CX RET bul ENDP dizi DB 3, 9, 7, 1, 4
Parametreler · Kodu adım adım çalıştır
Yığınla Parametre Geçirme (BP ile)
Çağıran, parametreleri yığına koyar; prosedür bunları BP üzerinden okur.
AX = 7.
1. parametre yığında: [FFFCh] = 0007h.
AX = 3.
2. parametre: [FFFAh] = 0003h.
CALL: dönüş adresi 010Bh → [FFF8h]. IP = 010Fh.
Eski BP saklanır: [FFF6h] = 0000h.
MOV BP, SP: BP = FFF6h sabit bir çerçeve noktası olur.
[BP+6] = [FFFCh] = 0007h → AX = 7.
[BP+4] = [FFFAh] = 0003h → AX = 000Ah.
BP eski değerine döner; SP = FFF8h (dönüş adresi üstte).
RET: IP = 010Bh, SP = FFFAh.
ADD SP, 4: iki parametre atılır, SP = FFFEh. Yığın dengelendi.
Program biter.
ORG 100h MOV AX, 7 PUSH AX ; 1. parametre MOV AX, 3 PUSH AX ; 2. parametre CALL TOPLA ADD SP, 4 ; parametreleri at RET TOPLA PROC PUSH BP MOV BP, SP MOV AX, [BP+6] ; 1. parametre ADD AX, [BP+4] ; 2. parametre POP BP RET TOPLA ENDP
[BP+0] = eski BP, [BP+2] = dönüş adresi, [BP+4] = son konan parametre. ADD SP, 4 yerine prosedürde RET 4 de yazılabilirdi.
Parametreler
Yığın Çerçevesi ve Yerel Değişkenler
PUSH BP MOV BP, SP SUB SP, 4 ; yerel alan MOV [BP-2], AX ; yerel 1 ... MOV SP, BP ; yerelleri at POP BP RET 4 ; 2 parametreyi at
SUB SP, 4yığında iki sözcüklük yerel alan açar: [BP−2], [BP−4].MOV SP, BPyerel alanı tek komutla geri verir.- Parametreler pozitif, yereller negatif BP bağıl konumundadır.
Parametreler · Kodu adım adım çalıştır
Kod İzleme: Yığında Yerel Değişken
Parametre x = 9 AX'te. Prosedür x'i bir yerel değişkende saklayıp x·x + x hesaplayacak.
CALL: dönüş adresi 0106h → [FFFCh].
PUSH BP: çağıranın BP'si (0000h) saklandı.
MOV BP, SP: BP = FFFAh, çerçevenin sabit noktası.
SUB SP, 2: bir sözcüklük yerel alan açıldı; [BP−2] = FFF8h.
MOV [BP−2], AX: yerel = 9. [BP...] adresleri varsayılan olarak SS kesimini kullanır.
MUL AX: AX = 81 (DX = 0). AX değişti ama x yerelde duruyor.
ADD AX, [BP−2]: 81 + 9 = 90 = 005Ah.
MOV SP, BP: yerel alan tek komutla bırakıldı, SP = FFFAh.
POP BP, RET: BP = 0, SP = FFFEh, çağırana dönüldü. Sonuç AX = 90.
MOV AX, 9 CALL kare_art ; AX = x·x + x RET kare_art PROC PUSH BP MOV BP, SP SUB SP, 2 ; yerel alan [BP-2] MOV [BP-2], AX ; yerel = x MUL AX ; AX = x·x ADD AX, [BP-2] ; + x MOV SP, BP ; yereli at POP BP RET kare_art ENDP
Örnek Programlar
Merhaba Dünya Mesajı
- Prosedür kullanarak ekrana mesaj yazdırma.
LEA SI, msg: msg adlı dizginin adresini SI yazmacına yükler (parametre = adres).CALL print_me: print_me prosedürünü çağırır.RET: işletim sistemine geri döner.print_me, null (0) ile biten bir dizgiyi karakter karakter yazdırır.b.[SI]emu8086'nınBYTE PTR [SI]kısaltmasıdır:CMP [SI], 0işlem boyutunu belirtmez, bu yüzden boyut açıkça yazılır.
ORG 100h LEA SI, msg ; msg adresi → SI CALL print_me RET ; DOS'a dön print_me PROC ; ............ print_me ENDP msg DB 'Hello World!', 0 END
Örnek Programlar · Kodu adım adım çalıştır
Kod İzleme: print_me
SI, dizginin ilk karakterini ('H') gösterir.
CALL print_me: dönüş adresi (4. satırdaki RET) yığına itilir.
[SI] = 'H' (48h) ≠ 0 → ZF = 0, JE atlamaz.
AL = 48h, AH = 0Eh, INT 10h → H.
SI = msg+1, next_char'a dön.
Aynı döngü: 'e' yazılır. AL = 65h, SI = msg+2.
Döngü 3 tur daha: "llo" yazılır. AL = 6Fh, SI = msg+5.
Döngü 6 tur daha: " World" yazılır (boşluk da bir karakterdir: 20h). AL = 64h, SI = msg+11.
Aynı döngü: '!' yazılır. AL = 21h, SI = msg+12.
[SI] = 0 → ZF = 1, JE stop atlar.
Prosedürün RET'i dönüş adresini alır → 4. satıra dönülür.
Ana programın RET'i DOS'a döner. Ekranda: Hello World!
ORG 100h LEA SI, msg ; SI = msg adresi CALL print_me RET ; DOS'a dön print_me PROC next_char: CMP b.[SI], 0 ; sıfır mı? JE stop ; evet: bitir MOV AL, [SI] ; sıradaki ASCII karakter MOV AH, 0Eh ; teletype işlevi INT 10h ; AL'yi ekrana yaz ADD SI, 1 ; sonraki karakter JMP next_char ; başa dön stop: RET ; çağırana dön print_me ENDP msg DB 'Hello World!', 0 END
Örnek Programlar · Kodu adım adım çalıştır
Kod İzleme: İki Değişken Toplama
Parametreler bellek değişkenlerinde: number1 = 5, number2 = 3.
CALL AddNumbers (0100h, 3 bayt): dönüş adresi 0103h itilir.
AX = number1 = 0005h.
AX = 5 + 3 = 0008h; sonuç sıfır değil, elde yok.
result = 0008h (bellekte 08 00).
RET → IP = 0103h.
RET → DOS. DW ? başlangıç değeri belirsiz demektir (emu8086 0 koyar).
ORG 100h start: CALL AddNumbers ; prosedürü çağır RET ; DOS'a dön AddNumbers PROC MOV AX, number1 ; AX = ilk sayı ADD AX, number2 ; AX = AX + ikinci sayı MOV result, AX ; sonucu sakla RET ; prosedürü bitir AddNumbers ENDP number1 DW 5 ; ilk sayı 5 number2 DW 3 ; ikinci sayı 3 result DW ? ; sonucu tutar
Örnek Programlar · Kodu adım adım çalıştır
Kod İzleme: Adres ve Sayaçla Parametre
Dizi toplamı, yeniden kullanılabilir bir prosedür olarak: SI = dizinin adresi.
CX = 5 eleman.
CALL topla: dönüş adresi yığına.
AL = 0.
+5 → AL = 5; SI ilerler, CX = 4.
+4 → AL = 9; SI ilerler, CX = 3.
+5 → AL = 14; SI ilerler, CX = 2.
+2 → AL = 16; SI ilerler, CX = 1.
+1 → AL = 17; SI ilerler, CX = 0 → döngü biter.
RET: çağırana dönülür, sonuç AL'de.
m = 11h = 17.
Program biter. Aynı prosedür başka bir dizi için yalnızca SI ve CX değiştirilerek çağrılabilir.
ORG 100h LEA SI, vector ; parametre 1: adres MOV CX, 5 ; parametre 2: adet CALL topla MOV m, AL ; dönüş değeri AL'de RET topla PROC ; Girdi: SI, CX MOV AL, 0 ; Çıktı: AL = toplam dongu: ADD AL, [SI] INC SI LOOP dongu RET topla ENDP vector DB 5, 4, 5, 2, 1 m DB 0
Örnek Programlar
Parametre Bloğu
LEA BX, blok CALL hesap ; sonuç = a × b + c RET hesap PROC ; Girdi: BX = blok adresi PUSH AX PUSH DX ; MUL DX'i de yazar MOV AX, [BX] ; a MUL WORD PTR [BX+2] ; × b ADD AX, [BX+4] ; + c MOV [BX+6], AX ; sonuç POP DX POP AX RET hesap ENDP blok DW 12, 7, 5, ?
- Tek bir yazmaç (BX) istenen sayıda parametreyi taşır: blok, sabit uzaklıklarla ([BX+2], [BX+4]) okunur.
- Sonuç da bloğa yazılır: 12 × 7 + 5 = 89 (0059h).
- Aynı prosedür başka bir blokla, yalnızca BX değiştirilerek çağrılır.
- Blok düzeni (hangi uzaklıkta ne var) prosedürün açıklamasında belgelenmelidir.
Örnek Programlar
Prosedürü Test Etmek
Bir prosedür yalnızca “olağan” girdiyle değil, sınır durumlarıyla da çalıştırılarak denenir. bul prosedürü (dizi: 3, 9, 7, 1, 4) için sonuçlar:
| Durum | AL | CX | CF | BX | Değerlendirme |
|---|---|---|---|---|---|
| ilk eleman | 3 | 5 | 1 | 0 | doğru |
| son eleman | 4 | 5 | 1 | 4 | doğru |
| dizide yok | 8 | 5 | 0 | 5 | doğru; BX anlamsızdır, yalnızca CF'ye bakılır |
| tek elemanlık arama | 9 | 1 | 0 | 1 | doğru: 9 ikinci eleman, aranan aralığın dışında |
| boş dizi | 7 | 0 | 1 | 2 | yanlış: CX = 0 iken LOOP 65 536 tur döner |
LOOP önce CX'i azaltır, sonra sıfır mı diye bakar: 0 − 1 = FFFFh. Prosedür dizinin ötesindeki baytları da tarar ve oradaki 7'yi “bulur”.
Döngüden önce JCXZ ile boş dizi ayrılır: CX = 0 ise doğrudan CLC yapılıp son etiketine gidilir. Ayrıca CX ve SI'nın korunduğu her testte denetlenir.
Örnek Programlar
İç İçe Çağrılar
main, A'yı çağırmak üzere. SP = FFFEh.
CALL A: 0103h itilir, SP = FFFCh.
A içinden CALL B: 0115h itilir, SP = FFFAh. Yığında iki dönüş adresi var.
B'nin RET'i en üstteki 0115h'yı alır: A'ya döner (LIFO).
A'nın RET'i 0103h'yı alır: main'e döner. Son çağrılan ilk döner.
Örnek Programlar
Yığın İhtiyacı Hesabı
- Bir programın yığın gereksinimi, çağrı ağacındaki en derin yol boyunca biriken baytların toplamıdır.
- Her yakın CALL 2 bayt dönüş adresi, her PUSH 2 bayt ekler.
| Yol | Hesap | Bayt |
|---|---|---|
| main → A → B | 2 + 4 + 2 + 2 | 10 |
| main → C | 2 + 6 | 8 |
- SP = FFFEh ile başlayan programda SP en çok FFF4h'ye iner.
- A ve C hiçbir zaman aynı anda yığında değildir: C çağrıldığında A'nın baytları çoktan geri alınmıştır.
Örnek Programlar
CALL / RET Simülatörü
CALL düğmeleri prosedür çağırır, "komut yürüt" IP'yi ilerletir, RET dönüşü yapar; yığındaki dönüş adresleri ve SP her adımda görünür.
Özyineleme
Özyineleme
- Özyineleme: bir prosedürün kendini (doğrudan ya da başka bir prosedür üzerinden) çağırmasıdır.
- Her çağrı yığına yeni bir dönüş adresi ve kendi sakladığı değerleri koyar: her düzeyin değerleri birbirinden ayrı kalır.
- Taban durum (durma koşulu) şarttır; yoksa her çağrı 2 bayt daha yer ve yığın taşar.
- Problem her adımda küçülerek kendine benzer: 472'yi yazmak = 47'yi yazmak + '2' yazmak.
- Sayı yazdırma, faktöriyel, ağaç ve dizin dolaşma özyinelemenin tipik örnekleridir.
Özyineleme · Kodu adım adım çalıştır
Kod İzleme: Özyinelemeli Sayı Yazdırma
AX = 472 (01D8h); CALL yaz: dönüş adresi 0106h yığına.
472 / 10: AX = 47 (2Fh), DX = 2. Son basamak (2) yığına itilir.
AX ≠ 0: yaz kendini çağırır; dönüş adresi 0116h (son etiketi) yığına.
İkinci düzey: 47 / 10 → AX = 4, basamak 7 yığına; yeniden CALL.
Üçüncü düzey: 4 / 10 → AX = 0, basamak 4 yığına. AX = 0: taban durum, JZ son. SP en derin noktada: FFF2h.
POP AX = 4 → '4' yazılır. En son itilen basamak en önce çıkar.
RET 0116h'ye, yani ikinci düzeyin son satırına döner: POP AX = 7 → '7'.
Birinci düzey: POP AX = 2 → '2'. Ekranda 472.
Son RET 0106h'ye döner; SP = FFFEh. Her düzey kendi basamağını yığında sakladığı için ek değişken gerekmedi.
ORG 100h MOV AX, 472 CALL yaz RET yaz PROC ; Girdi: AX (işaretsiz) XOR DX, DX MOV BX, 10 DIV BX ; AX = bölüm, DX = basamak PUSH DX OR AX, AX ; başka basamak var mı? JZ son CALL yaz ; önce üst basamaklar son: POP AX ADD AL, '0' MOV AH, 0Eh INT 10h RET yaz ENDP
Özyineleme
Özyineleme mi, Döngü mü?
| Ölçüt | Özyineleme (yaz) | Döngü (bölme + LOOP) |
|---|---|---|
| Kod | kısa; problemin tanımına yakın | sayaç ve ayrı bir yazdırma döngüsü gerekir |
| Yığın | her düzeyde dönüş adresi + basamak: 4 bayt | basamak başına 2 bayt |
| Süre | her düzeyde CALL + RET (19 + 8 tur) | çağrı yükü yok |
| Risk | derinlik sınırsız büyürse yığın taşar | yığın kullanımı sınırlı |
yaz için düzey sayısı = basamak sayısı. 65 535 → 5 düzey: 5 × 2 (dönüş) + 5 × 2 (basamak) = 20 bayt; SP en çok FFEAh'ye iner.
Denetim CALL'dan önce yapılmalıdır. AX = 0 için de bir basamak (0) yazılır: 0 sayısı ek kod gerektirmeden '0' olarak çıkar.
Sık Yapılan Hatalar · Kodu adım adım çalıştır
Kod İzleme: PUSH / POP Dengesizliği
AX = 1234h.
CALL: dönüş adresi 0106h itilir.
PUSH AX: 1234h, dönüş adresinin üstüne itilir.
RET yığının tepesindekini alır: IP = 1234h! İşlemci rastgele bir adrese atlar, program çöker ya da öngörülemez davranır.
ORG 100h MOV AX, 1234h ; 0100h: B8 34 12 CALL hatali ; 0103h: dönüş = 0106h RET ; 0106h hatali PROC PUSH AX ; AX saklandı... ; ... işlemler ... RET ; POP AX unutuldu! hatali ENDP
Sık Yapılan Hatalar · Kodu adım adım çalıştır
Kod İzleme: Yakın CALL, Uzak Dönüş
AX = 5. uzak prosedürü RETF ile bitiyor, ama çağrı yakın.
Yakın CALL yalnızca IP'yi (0106h) iter: yığında 2 bayt.
SHL AX, 1: AX = 000Ah.
RETF iki sözcük çeker: IP = 0106h ve CS olarak yığında bulduğu değer, burada DOS'un .COM için ittiği 0000h. SP = 0000h.
İşlemci 0000:0106h'ye atlar: burası kesme vektör tablosudur, program kodu değil. Program çöker; ADD AX, 1 hiç çalışmaz.
ORG 100h MOV AX, 5 CALL uzak ; yakın CALL: yalnız IP ADD AX, 1 RET uzak PROC SHL AX, 1 RETF ; IP ve CS çeker! uzak ENDP
Sık Yapılan Hatalar
Sık Yapılan Hatalar
p1 PROC … p2 ENDP → assembler hatası.Sık Yapılan Hatalar
Daha Fazla Prosedür Hatası
MOV SI, dizi adresi değil dizinin ilk sözcüğünü yükler; prosedür ilgisiz bir adresten okur.Özet
Özet
Özet
Alıştırmalar
- 0120h adresindeki
CALL pkomutu 0100h'daki p'yi çağırıyor. Makine kodu nedir? Yığına ne itilir? - m2 örneğinde ikinci
CALL m2'den önceMOV AL, 3eklenseydi son AX ne olurdu? - SP = FFFEh iken art arda 3 CALL yapılıyor (hiç RET yok). SP kaç olur?
- CX'i bozan bir prosedürü LOOP içinde çağırmak neden hatalıdır? İki çözüm önerin.
- AL'deki küçük harfi büyük harfe çeviren
buyukprosedürünü yazın (girdi/çıktı AL). - print_me'yi, yazdığı karakter sayısını CX'te döndürecek şekilde değiştirin.
İpuçları / cevaplar
- Sonraki adres 0123h; 0100h − 0123h = −23h = FFDDh →
E8 DD FF. Yığına 0123h itilir. - İlk çağrı AX = 2; sonra AL = 3 → AX = 3 × 2 = 0006h.
- Her CALL 2 bayt: FFFEh − 6 = FFF8h.
- LOOP sayacı bozulur, döngü yanlış sayıda döner. Prosedürde
PUSH CX/POP CXya da çağırmadan önce çağıranda saklama. cmp al,'a'/jb son/cmp al,'z'/ja son/sub al, 20h/son: ret- Başta
MOV CX, 0, her karakterdeINC CX; CX çıktı olduğu için korunmaz.
Özet
Kaynaklar
- emu8086 belgeleri: 8086 Assembler Tutorial for Beginners (Part 8: Procedures).
- Intel, The 8086 Family User's Manual, 1979: CALL, RET ve yığın işlemleri.
- Barry B. Brey, The Intel Microprocessors, 8. baskı, Pearson, 2009: Bölüm 6.3 "Procedures".
- Kip R. Irvine, Assembly Language for x86 Processors, 7. baskı, Pearson, 2014: Bölüm 5 "Procedures".