Mikroişlemciler · Ders Notu
Bölüm 7: Akış Kontrolü
İşlemci, kendi hâline bırakıldığında komutları bellekteki sırasıyla yürütür. Her komuttan sonra komut işaretçisi IP, komutun uzunluğu kadar artar ve bir sonraki komutu gösterir. Karar veren, bir işi tekrarlayan ya da bir hata durumunda başka yola sapan her program bu düz çizgiyi kırmak zorundadır. Akış kontrol komutları bunu IP'ye yeni bir değer yükleyerek yapar. Bu not önce koşulsuz atlamayı (JMP) ve atlama uzaklığının makine koduna nasıl yazıldığını anlatır. Ardından bayraklara bakan koşullu atlamalar, CMP komutu ve işaretli ile işaretsiz sayıların neden ayrı atlama ailelerine ihtiyaç duyduğu gelir. Yüksek düzeyli dillerdeki if/else ve while yapılarının assembly karşılıkları kurulur. Son olarak CX yazmacını sayaç olarak kullanan LOOP ailesi ve JCXZ incelenir. Her örnek program yazmaç, bayrak ve bellek düzeyinde adım adım izlenir; atlamanın önden getirme kuyruğu ve saat turları üzerindeki etkisi de ayrıca açıklanır.
Öğrenme çıktıları
- Atlama mekanizması
- JMP ve etiketlerle programın akışını yönlendirmek; IP'nin nasıl değiştiğini ve atlama uzaklığının (bağıl konumun) nasıl hesaplandığını açıklamak; kısa, yakın, uzak ve dolaylı atlamayı ayırt etmek.
- Koşullu atlamalar
- Her koşullu atlamanın hangi bayrağa (ZF, CF, SF, OF, PF) baktığını söylemek, zıt komutunu ve eş anlamlılarını bulmak, makine kodunu hesaplamak.
- Doğru karşılaştırma
- CMP sonrasında işaretsiz sayılar için JA/JB, işaretli sayılar için JG/JL ailesini doğru seçmek; SF = OF kuralının nedenini taşma üzerinden açıklamak.
- Yüksek düzey yapılar
- if/else, while ve for yapılarını koşulun tersiyle atlayan kalıplarla yazmak; menzil dışı koşullu atlamayı ters koşul ve JMP ile çözmek.
- Döngüler
- LOOP, LOOPE/LOOPZ, LOOPNE/LOOPNZ ve JCXZ ile sayaçlı döngüler yazmak; CX = 0 tuzağını ve iç içe döngüde sayacı saklama gereğini açıklamak; örnek programları adım adım izlemek.
7.1Sıralı yürütme ve akış kontrolü
7.1.1IP ve önden getirme kuyruğu
8086'nın yürüteceği komutlar bellekte, kod kesiminde art arda dizilmiş baytlardır. İşlemci bu baytların hangisinin sıradaki komut olduğunu iki yazmaçla bilir: CS (kod kesimi) ve IP (komut işaretçisi, instruction pointer). CS kesimin başlangıcını, IP ise sıradaki komutun bu kesim içindeki bağıl konumunu tutar. Komutun fiziksel adresi, öteki bellek erişimlerinde olduğu gibi CS × 10h + IP ile bulunur. Bir .COM programında CS = 0700h ve ilk komut 0100h'de ise işlemci ilk komutu 07000h + 0100h = 07100h adresinden okur.
Programcı IP'ye MOV ile değer yazamaz; MOV IP, AX diye bir komut yoktur. IP'yi yalnız işlemcinin kendisi değiştirir. Olağan durumda değişiklik basittir: bir komut çözülünce IP o komutun bayt sayısı kadar artar. Üç baytlık MOV AX, 5 (B8 05 00) 0100h'deyse komut bitince IP = 0103h olur. İki baytlık MOV AL, 25 (B0 19) IP'yi iki artırır. Bu düzene sıralı yürütme denir.
Sıralı yürütmenin 8086'da bir donanım karşılığı vardır. İşlemci iki birimden oluşur. Yol arayüz birimi (BIU, bus interface unit) adres ve veri yollarını yönetir; komut baytlarını bellekten okur. Yürütme birimi (EU, execution unit) bu baytları çözer ve işlemi yapar. BIU, EU'nun işini bitirmesini beklemez. Yol boş kaldığında CS:IP'nin ilerisindeki baytları önceden okur ve altı baytlık önden getirme kuyruğuna (prefetch queue) koyar. EU sıradaki komuta geçtiğinde baytlar çoğu zaman kuyrukta hazırdır. Böylece bellekten okuma ile komut yürütme aynı anda yapılır. Bu düzen basit bir iş hattıdır (pipeline).
Kuyruğun bir varsayımı vardır: sıradaki komut bellekte hemen arkadan gelir. Akış kontrol komutları bu varsayımı bozar. Bir atlama yürütüldüğünde IP başka bir adrese gider. Kuyruktaki baytlar artık yanlış komutlara aittir. EU kuyruğu boşaltır ve BIU yeni CS:IP'den okumaya baştan başlar. Atlamanın saat turu bedeli büyük ölçüde bu yeniden doldurma işinden gelir.
Komut işaretçisi (IP), yürütülecek sıradaki komutun kod kesimindeki bağıl konumunu tutan 16 bitlik yazmaçtır. Komutun fiziksel adresi CS × 10h + IP'dir. Akış kontrol komutu (flow control instruction), IP'ye (uzak atlamada CS'ye de) komut uzunluğundan farklı bir değer yükleyen komuttur.
7.1.2Akış kontrol komutlarının türleri
8086'nın akış kontrol komutları üç gruba ayrılır. Birinci grup koşulsuz atlamadır: JMP etiket her durumda atlar. İkinci grup koşullu atlamalardır (Jcc). Bunlar bayrak yazmacındaki bir ya da birkaç bayrağa bakar; koşul doğruysa atlar, değilse sıradaki komuta geçer. Koşullu atlamalar da kendi içinde üçe bölünür. Bunlar tek bayrağı sınayanlar (JZ, JC, JS, JO, JP ve tersleri), işaretli sayılar için olanlar (JG, JL ailesi) ve işaretsiz sayılar için olanlardır (JA, JB ailesi). Üçüncü grup döngü komutlarıdır: LOOP, LOOPE/LOOPZ, LOOPNE/LOOPNZ ve JCXZ. Bunlar sayaç olarak CX'i kullanır.
Akışı değiştiren başka komutlar da vardır. CALL ile RET bir alt programa gidip geri dönmeyi sağlar; dönüş adresini yığında saklar. INT ile IRET kesme hizmetine gidip dönmeyi sağlar. Bu notta her programın sonunda görülen RET de bir akış kontrol komutudur. Bir .COM programı başladığında yığının tepesinde 0000h vardır. RET bu değeri IP'ye yükler. 0000h bağıl konumunda DOS'un programın başına koyduğu PSP (program kesimi öneki) bulunur. PSP'nin ilk iki baytı INT 20h'dir ve program böylece sona erer. İzleme tablolarındaki son satırda IP = 0000h ve SP = 0000h görülmesinin nedeni budur.
Akış kontrolünün yüksek düzeyli dillerdeki karşılığı karar yapıları (if/else, switch) ve döngülerdir (for, while, do-while). Derleyici bunların hepsini karşılaştırma, koşullu atlama ve koşulsuz atlama komutlarına çevirir. Assembly ile çalışan programcı bu çeviriyi elle yapar. Şekil 7.1, aynı program parçasının sıralı yürütmede ve atlamalı yürütmede nasıl gezildiğini karşılaştırır.
jmp komutları IP'ye yeni değer yükler; akış calc, back ve stop etiketlerine sıçrar.7.2Koşulsuz atlama: JMP
7.2.1JMP ve biçimleri
JMP etiket komutu denetimi programın başka bir noktasına her koşulda aktarır. İleri de atlayabilir, geri de. Komut hiçbir bayrağı değiştirmez; atlamadan önceki bayrak durumu atlamadan sonra da aynen geçerlidir. Bu özellik önemlidir: bir CMP'nin ayarladığı bayraklar araya giren bir JMP'den sonra hâlâ okunabilir.
Koşulsuz atlama (unconditional jump), yürütmenin bayraklara bakılmaksızın belirtilen hedef adresten sürmesidir. 8086'da JMP komutuyla yapılır. Hedef komutun içinde ya bir uzaklık (jump displacement) olarak, ya mutlak bir CS:IP çifti olarak ya da bir yazmaç veya bellek sözcüğünde dolaylı olarak verilir.
8086 JMP komutunun dört biçimini tanır. Hangisinin kullanılacağı hedefin ne kadar uzakta olduğuna ve nerede yazılı olduğuna bağlıdır. Tablo 7.1 bu biçimleri özetler.
| Biçim | Kod | Bayt | Menzil | Değişen | Saat turu |
|---|---|---|---|---|---|
kısa (JMP SHORT) | EB rel8 | 2 | −128…+127 | IP | 15 |
yakın (JMP NEAR) | E9 rel16 | 3 | kesim içi her yer | IP | 15 |
uzak (JMP FAR) | EA ofs seg | 5 | 1 MB'ın her yeri | CS ve IP | 15 |
dolaylı, yazmaç (JMP BX) | FF E3 | 2 | kesim içi | IP | 11 |
dolaylı, bellek (JMP [BX]) | FF 27 | 2–4 | kesim içi | IP | 18 + EA |
Kısa atlama (short jump) iki bayttır: işlem kodu EBh ve 8 bitlik işaretli uzaklık (rel8). Uzaklık −128 ile +127 arasında olabilir. Bu aralık, atlama komutundan hemen sonraki komutun adresine göre sayılır. Döngülerin ve kısa if bloklarının çoğu bu aralığa sığar, bu yüzden kısa atlama en sık görülen biçimdir.
Yakın atlama (near jump) üç bayttır: E9h ve 16 bitlik uzaklık (rel16). 16 bitlik toplama sarmalı olduğundan rel16 ile kod kesiminin 64 KB'lık alanındaki her bağıl konuma (0000h–FFFFh) ulaşılır. Uzaklık işaretli yorumlandığında bu ±32 KB demektir; ama sarma sayesinde kesimin öbür ucuna da gidilebilir. Kısa ve yakın atlamada yalnız IP değişir, CS aynı kalır. Bu yüzden ikisi de kesim içi (intrasegment) atlamadır.
Uzak atlama (far jump) beş bayttır: EAh, ardından hedefin 16 bitlik bağıl konumu ve 16 bitlik kesim değeri. Bayt sırası little-endian'dır; önce bağıl konumun düşük baytı gelir. Örneğin 0800h:0000h adresine uzak atlama EA 00 00 00 08 olarak kodlanır. Komut IP'ye 0000h, CS'ye 0800h yükler ve yürütme 08000h fiziksel adresinden sürer. Bu, başka bir kesime geçmenin tek yoludur ve kesimler arası (intersegment) atlama diye anılır.
Dolaylı atlamada hedef komutun içinde yazılı değildir. JMP BX IP'ye BX'in değerini yükler. JMP [BX] ise DS:BX adresindeki sözcüğü okur ve onu IP'ye yükler. Dolaylı atlama, hedefin çalışma anında hesaplandığı durumlar için uygundur. Bir menüde seçilen numaraya göre farklı bir işleve gitmek bunun tipik örneğidir. Programcı bir atlama tablosu kurar: tablo dw is0, is1, is2. Seçim numarası 2 ile çarpılıp SI'ya konur ve JMP tablo[SI] doğru adrese gider. Bu komutun makine kodu FF A4 ile tablonun 16 bitlik adresinden oluşur ve dört bayttır.
Programcının çoğu zaman biçim seçmesi gerekmez. Assembler hedefi biliyorsa ve hedef kısa menzildeyse 2 baytlık kısa biçimi kendisi seçer. Hedef kodda daha aşağıda ve henüz tanımlanmamışsa bazı assembler'lar önce 3 bayt yer ayırır. Çok geçişli assembler'lar (emu8086 bunlardan biridir) adresleri kesinleştirdikten sonra kısa biçime iner. JMP SHORT etiket yazımı kısa biçimi zorlar; hedef menzil dışındaysa assembler hata verir. JMP NEAR PTR etiket yazımı ise hedef yakında olsa bile 3 baytlık biçimi seçtirir.
“JMP en çok 127 bayt ileri gidebilir” yargısı yanlıştır. Bu sınır yalnız kısa biçim içindir. Yakın JMP kod kesiminin her yerine, uzak JMP ise 1 MB'lık adres alanının her yerine ulaşır. 127 baytlık sınır asıl olarak 8086'nın koşullu atlamaları için önemlidir, çünkü onların 16 bitlik biçimi yoktur.
7.2.2Etiketler
Assembly programcısı atlama hedefini adresle değil etiketle (label) yazar. Etiket, bir adrese verilmiş addır. Tanımlamak için ad yazılır ve sonuna iki nokta konur: dongu:. Etiket ayrı bir satırda durabilir ya da bir komutun önüne yazılabilir: back: jmp stop. İki yazımda da etiket, kendisinden sonra gelen ilk komutun adresini alır.
Etiket bellekte yer kaplamaz. Assembler kaynak kodu okurken bir konum sayacı tutar. Her komutun bayt sayısını bu sayaca ekler ve bir etiketle karşılaşınca sayacın o anki değerini etiketin adresi olarak kaydeder. Sonra jmp dongu gibi bir satırı kodlarken bu adresi kullanır. Şekil 7.2'de dongu etiketi, kendisini izleyen DEC CX komutunun adresine, 0103h'ye dönüşür. Makine kodunda dongu diye bir şey yoktur; yalnız JNZ komutunun uzaklık baytında bu adrese giden bir sayı vardır.
dongu etiketi, ardından gelen DEC CX komutunun adresine (0103h) dönüşür; JNZ dongu bu adrese atlar. Etiket bellekte bayt kaplamaz.Etiket adları için birkaç kural vardır. Ad harf, rakam ve alt çizgi gibi karakterlerden oluşur, ama rakamla başlayamaz. Ad boşluk içeremez. Komut adları (mov, loop) ve yazmaç adları (ax, cx) etiket olamaz, çünkü assembler bir satırın başındaki sözcüğü önce komut ya da yazmaç olarak tanımaya çalışır. Aynı etiket bir programda bir kez tanımlanır; ikinci tanım hangi adresin kastedildiğini belirsizleştirir ve hataya yol açar.
| Etiket | Durum | Neden |
|---|---|---|
dongu: | geçerli | harflerden oluşur |
next_char: | geçerli | alt çizgi kullanılabilir |
k1:, calc: | geçerli | rakam ilk karakter değil |
1basla: | geçersiz | rakamla başlıyor |
mov: | geçersiz | komut adı |
iki kelime: | geçersiz | boşluk içeriyor |
Etiketin bir türü de vardır. Kod içindeki etiketler yakın türdedir: yalnız bağıl konumu taşırlar ve bu yüzden kesim içi atlamalarda kullanılır. Veri tanımlarının adları (msg db 'Hello', 0) da birer adrestir, ama bunlara değişken denir ve veri türleri (bayt, sözcük) vardır. Bu fark JMP'de önem kazanır. jmp calc yazıldığında assembler calc'ın adresine giden bir uzaklık üretir. tablo dw 0103h gibi bir değişkenin adı yazıldığında ise jmp tablo, dolaylı atlama olarak kodlanır (FF 26 ve tablonun adresi). İşlemci tablonun içindeki sözcüğü okur ve onu IP'ye yükler. Yazım birbirine benzese de biri etikete, öbürü değişkenin içeriğine gider.
7.2.3Kod izleme: JMP ile ileri ve geri
Aşağıdaki program JMP'nin hem ileri hem geri atlayabildiğini gösterir. Program iki sayı yükler, toplamayı yapan calc bloğuna ileri atlar, oradan geriye back satırına döner ve son olarak yeniden ileri, stop satırına gider. Kaynakta satırların sırası ile yürütülme sırası aynı değildir.
org 100h
mov ax, 5 ; ax = 5
mov bx, 2 ; bx = 2
jmp calc ; 'calc' etiketine git
back: jmp stop ; 'stop' etiketine git
calc:
add ax, bx ; ax = ax + bx
jmp back ; 'back' etiketine dön
stop:
ret ; işletim sistemine dönorg 100h yönergesi bir komut değildir. Assembler'a konum sayacını 0100h'den başlatmasını söyler, çünkü DOS bir .COM programını kesimin 0100h bağıl konumuna yükler. İlk komut mov ax, 5 bu yüzden 0100h'dedir. İşlemci programa başlarken IP = 0100h'dir.
Tablo 7.3 programın her komutunu, adresini, makine kodunu ve komut bittikten sonraki durumu verir. Son sütundaki IP, sıradaki komutun adresidir.
| Adım | Adres | Komut | Kod | AX | BX | IP (sonra) |
|---|---|---|---|---|---|---|
| 0 | — | başlangıç | — | 0000h | 0000h | 0100h |
| 1 | 0100h | mov ax, 5 | B8 05 00 | 0005h | 0000h | 0103h |
| 2 | 0103h | mov bx, 2 | BB 02 00 | 0005h | 0002h | 0106h |
| 3 | 0106h | jmp calc | EB 02 | 0005h | 0002h | 010Ah |
| 4 | 010Ah | add ax, bx | 01 D8 | 0007h | 0002h | 010Ch |
| 5 | 010Ch | jmp back | EB FA | 0007h | 0002h | 0108h |
| 6 | 0108h | jmp stop | EB 04 | 0007h | 0002h | 010Eh |
| 7 | 010Eh | ret | C3 | 0007h | 0002h | 0000h |
İlk iki adım sıralı yürütmedir: IP önce 3, sonra yine 3 artar. Üçüncü adımda jmp calc yürütülür. Sıralı yürütmede IP 0108h olurdu. Atlama IP'ye calc'ın adresini, 010Ah'yi yükler. Arada kalan 0108h'deki back: jmp stop komutu bu turda yürütülmez. Dördüncü adımda add ax, bx AX'i 5 + 2 = 7 yapar. ADD bayrakları da etkiler. Sonuç sıfır değildir (ZF = 0), işaret biti 0'dır (SF = 0), elde ve taşma yoktur (CF = OF = 0). 07h = 0000 0111b üç tane 1 içerdiği için eşlik tek (PF = 0).
Beşinci adım geriye atlamadır. jmp back 010Ch'dedir ve IP'yi 0108h'ye, yani kendinden önceki bir adrese çeker. Altıncı adımda 0108h'deki jmp stop nihayet yürütülür ve IP 010Eh olur. Yedinci adımda ret yığından 0000h değerini alır; IP = 0000h, SP = FFFEh + 2 = 0000h olur. PSP'deki INT 20h programı bitirir. Sonuç AX = 0007h'dir. Yürütme sırası kaynak satırlarına göre 2 → 3 → 4 → 7 → 8 → 5 → 10'dur. Üç JMP de bayraklara dokunmadığından ADD'in ayarladığı bayraklar programın sonuna kadar aynı kalır.
Tablodaki add ax, bx için verilen 01 D8, ADD'in “yazmaç/bellek ← yazmaç” biçimidir. Aynı komut “yazmaç ← yazmaç/bellek” biçimiyle 03 C3 olarak da kodlanabilir ve bazı assembler'lar bunu üretir. İki kodlama da iki bayttır ve aynı işi yapar; adresler ve atlama uzaklıkları değişmez.
7.2.4Atlama uzaklığının hesabı
Kısa ve yakın atlamanın makine kodunda hedefin adresi yazılı değildir. Onun yerine hedefin, atlama komutundan sonraki komutun adresine göre işaretli uzaklığı yazılıdır. Bu yaklaşıma göreceli adresleme (relative addressing) denir. Hesap şöyledir:
uzaklık = hedef − (atlama komutunun adresi + atlama komutunun uzunluğu)
Parantez içindeki toplam, işlemcinin atlama komutunu okuyup bitirdiği andaki IP'dir. İşlemci atlamayı yürütürken bu IP'ye uzaklığı ekler: IP ← IP + uzaklık. Uzaklık negatifse ikiye tümleyen gösterimle saklanır ve toplama yine aynı devreyle yapılır.
Atlama uzaklığı (jump displacement), atlama hedefinin, atlama komutundan sonraki komutun adresine göre işaretli farkıdır. Kısa atlamada ve 8086'nın bütün koşullu atlamalarında 8 bitlik (rel8, −128…+127), yakın atlamada 16 bitliktir (rel16). İşlemci yürütürken IP ← IP + işaret genişletilmiş uzaklık hesabını yapar.
Şekil 7.3, JMP örneğindeki ilk atlamanın, jmp calc komutunun kodunu çıkarır. Komut 0106h'dedir ve iki bayttır. İşlemci komutu bitirdiğinde IP = 0108h'dir. Hedef calc = 010Ah'dir. Uzaklık 010Ah − 0108h = +2'dir ve makine kodu EB 02 olur. Bu iki bayt, 0108h'deki iki baytlık jmp stop komutunun üzerinden atlamak demektir.
jmp calc 0106h'de, sonraki komut 0108h'de, hedef 010Ah'de. Uzaklık 010Ah − 0108h = +2, kod EB 02.0108h'deki jmp stop için hesap aynıdır. Sonraki komut 010Ah'dedir, hedef stop = 010Eh'dir. Uzaklık 010Eh − 010Ah = +4, kod EB 04'tür. İşlemci yürütürken IP ← 010Ah + 4 = 010Eh hesabını yapar.
Geri atlamada uzaklık negatiftir. 010Ch'deki jmp back komutundan sonra IP = 010Eh'dir; hedef back = 0108h'dir. Uzaklık 0108h − 010Eh = −6'dır. −6'nın 8 bitlik ikiye tümleyeni şöyle bulunur: 6 = 0000 0110b; bitler ters çevrilince 1111 1001b; 1 eklenince 1111 1010b = FAh. Makine kodu EB FA'dır (Şekil 7.4).
jmp back 010Ch'de, sonraki IP 010Eh, hedef 0108h. Uzaklık −6, ikiye tümleyenle FAh; kod EB FA.İşlemci bu sekiz bitlik sayıyı IP'ye eklemeden önce işaret genişletme yapar: en anlamlı bit 1 olduğundan üst sekiz bit 1'lerle doldurulur ve FAh, FFFAh olur. Toplama 16 bitliktir: 010Eh + FFFAh = 1 0108h. On yedinci bit yazmaca sığmaz ve atılır; IP = 0108h olur. Pozitif uzaklıkta da aynı işlem yapılır, yalnız üst bitler 0'larla dolar: 02h → 0002h.
0150h adresindeki kısa JMP 0140h'ye atlamak istiyor. Makine kodu nedir?
Komut iki bayttır, sonraki IP = 0152h. Uzaklık = 0140h − 0152h = −12h = −18. 18 = 0001 0010b; tersi 1110 1101b; 1 eklenince 1110 1110b = EEh. Kod EB EE'dir. Sağlama: 0152h + FFEEh = 1 0140h → IP = 0140h.
Aynı hedefe 0100h'den gidilseydi uzaklık 0140h − 0102h = +3Eh olurdu; kod EB 3E. 0300h'den gidilseydi uzaklık 0140h − 0302h = −1C2h = −450 olurdu. Bu sayı −128'den küçüktür; kısa biçim kullanılamaz. Assembler yakın biçimi seçer: komut 3 bayt, sonraki IP = 0303h, uzaklık 0140h − 0303h = −1C3h = FE3Dh; kod E9 3D FE.
Göreceli adreslemenin önemli bir yararı vardır. Kod bellekte başka bir yere taşındığında uzaklıklar değişmez, çünkü hem atlama komutu hem hedefi aynı miktarda kayar. Böyle koda konumdan bağımsız kod denir. Bir .COM programını DOS hangi kesime yüklerse yüklesin, kesim içindeki kısa ve yakın atlamalar doğru çalışır. Uzak atlamada ise hedef mutlak bir kesim değeri içerir; program taşınırsa bu değerin düzeltilmesi gerekir.
7.2.5Atlamanın donanımdaki bedeli
Tablo 7.1'de JMP'nin 15 saat turu sürdüğü görülür. Bu, iki baytlık bir komut için uzun bir süredir; iki yazmacı toplayan ADD AX, BX yalnız 3 tur sürer. Farkın nedeni önden getirme kuyruğudur. Atlama yürütüldüğünde EU, BIU'ya kuyruğu boşaltmasını ve yeni adresten okumaya başlamasını söyler. BIU yeni CS:IP'yi adres yoluna çıkarır ve bir yol döngüsüyle (dört saat turu) iki bayt okur. EU, yeni komutun ilk baytı kuyruğa gelene kadar bekler. 15 turun önemli bir kısmı bu bekleyişten oluşur.
Koşullu atlamalarda bu bedel daha açık görülür. Koşul sağlanır ve atlama yapılırsa komut 16 tur sürer. Koşul sağlanmazsa yalnız 4 tur sürer, çünkü IP sıradaki komutu gösterir ve kuyruktaki baytlar geçerliliğini korur. Aynı komutun iki durumu arasında dört kat fark vardır. Bu yüzden sık yürütülen döngülerde atlamanın “çoğunlukla atlanmayan” tarafa düşecek biçimde yazılması 8086'da bile hız kazandırır.
| Komut | Atlarsa | Atlamazsa |
|---|---|---|
JMP kısa / yakın / uzak | 15 | — |
JMP yazmaç / bellek | 11 / 18 + EA | — |
Jcc (bütün koşullu atlamalar) | 16 | 4 |
LOOP | 17 | 5 |
LOOPE / LOOPZ | 18 | 6 |
LOOPNE / LOOPNZ | 19 | 5 |
JCXZ | 18 | 6 |
Bu sayılar 5 MHz'lik bir 8086'da somut bir anlam kazanır. Bir saat turu 1 / 5 MHz = 200 ns'dir. 15 turluk bir JMP 3 µs sürer. Atlanmayan bir koşullu atlama 0,8 µs, atlanan bir koşullu atlama 3,2 µs sürer. Bin turluk bir döngünün yalnız döngü komutu bu yüzden milisaniyeler mertebesinde zaman tutabilir.
Atlamanın bir yan etkisi daha vardır. Hedef adres tek bir sayıysa BIU ilk yol döngüsünde yalnız bir bayt getirebilir. 8086'nın veri yolu 16 bittir ve bir sözcüğü tek yol döngüsünde ancak çift adresten okuyabilir. Sık kullanılan döngü başlarını çift adrese hizalamak (EVEN yönergesi) bu yüzden küçük bir kazanç sağlar. 8 bitlik veri yolu olan 8088'de bu fark yoktur, ama her bayt için ayrı yol döngüsü gerektiğinden kuyruğu doldurmak daha da uzun sürer.
7.3Koşullu atlamalar
7.3.1Koşullu atlama fikri
Koşulsuz atlama tek başına karar veremez; her seferinde aynı yere gider. Karar için atlamanın bir koşula bağlanması gerekir. 8086'da bu koşul her zaman bayrak yazmacındaki bayraklardan okunur. Koşullu atlama komutlarının genel adı Jcc'dir; cc yerine koşulun kısaltması gelir (Z, NZ, C, A, G …). Jcc etiket komutunun anlamı şudur: koşul doğruysa IP ← etiket, değilse IP sıradaki komutu göstermeye devam eder.
Koşullu atlama (conditional jump), bayrak yazmacındaki bir ya da birkaç bayrağın durumuna göre ya hedefe atlayan ya da sıradaki komuta geçen komuttur. Koşullu atlama bayrakları yalnız okur; hiçbirini değiştirmez.
Bayrakları koşullu atlamadan önce gelen bir komut ayarlar. Bu komut çoğunlukla CMP'dir, ama SUB, ADD, TEST, AND, INC, DEC gibi aritmetik ve mantık komutları da bayrakları ayarlar. Bayrakları ayarlayan komut ile onları okuyan atlama arasına bayrağa dokunmayan komutlar (MOV, JMP, LOOP, PUSH, POP) girebilir; karar yine ilk komutun sonucuna göre verilir. Araya bayrağı değiştiren bir komut girerse karar o komuta göre verilir. Bu, akış kontrolündeki hataların başlıca kaynaklarından biridir.
Atlamalarda kullanılan durum bayrakları şunlardır:
- ZF (sıfır bayrağı): sonuç sıfırsa 1.
- CF (elde bayrağı): toplamada en anlamlı bitten elde çıkarsa, çıkarmada ödünç alınırsa 1.
- SF (işaret bayrağı): sonucun en anlamlı biti; işaretli yorumda sonuç negatifse 1.
- OF (taşma bayrağı): işaretli yorumda sonuç hedefin bit sayısına sığmıyorsa 1.
- PF (eşlik bayrağı): sonucun düşük baytında çift sayıda 1 varsa 1.
- AF (yardımcı elde bayrağı): bit 3'ten elde ya da ödünç varsa 1. Bu bayrağı sınayan bir atlama yoktur; AF yalnız BCD düzeltme komutlarında (DAA, DAS, AAA, AAS) kullanılır.
Şekil 7.5 koşullu atlamanın akışını özetler. Bayrakları ayarlayan komuttan sonra işlemci bir yol ayrımına gelir. Koşul sağlanıyorsa etikete, sağlanmıyorsa sıradaki komuta gider. Yüksek düzeyli dillerdeki her if bu yol ayrımının bir örneğidir.
7.3.2Tek bayrağı sınayan atlamalar
En basit koşullu atlamalar tek bir bayrağa bakar. Her bayrak için iki komut vardır: biri bayrak 1 iken, öbürü bayrak 0 iken atlar. Komut adındaki N harfi “not” (değil) anlamına gelir: JZ sıfırsa, JNZ sıfır değilse atlar. Tablo 7.5 bu komutları, koşullarını ve zıtlarını verir.
| Komut | Anlamı | Koşul | Zıt komut |
|---|---|---|---|
JZ, JE | sıfırsa / eşitse (Jump if Zero / Equal) | ZF = 1 | JNZ, JNE |
JC, JB, JNAE | elde varsa / aşağıdaysa | CF = 1 | JNC, JNB, JAE |
JS | sonuç negatifse | SF = 1 | JNS |
JO | işaretli taşma varsa | OF = 1 | JNO |
JPE, JP | eşlik çiftse | PF = 1 | JPO, JNP |
JNZ, JNE | sıfır değilse / eşit değilse | ZF = 0 | JZ, JE |
JNC, JNB, JAE | elde yoksa / aşağıda değilse | CF = 0 | JC, JB, JNAE |
JNS | sonuç negatif değilse | SF = 0 | JS |
JNO | taşma yoksa | OF = 0 | JO |
JPO, JNP | eşlik tekse | PF = 0 | JPE, JP |
Bu komutların kullanım alanları farklıdır. JZ/JNZ en sık kullanılanlardır: bir sayacın sıfıra inip inmediği, iki değerin eşit olup olmadığı, bir dizginin sonundaki 0 baytına gelinip gelinmediği bunlarla sınanır. JC/JNC işaretsiz toplamada taşmayı, çıkarmada ödünç almayı yakalar. DOS ve BIOS hizmetlerinin bazıları da hata durumunu CF ile bildirir; hizmet çağrısından sonra JC hata yazılır. JS/JNS bir sonucun işaretine bakar. JO/JNO işaretli aritmetikte taşmayı yakalar. JP/JNP seyrek kullanılır; seri iletişimde eşlik denetimi gibi işlerde işe yarar.
JC adındaki C, elde (carry) bayrağıdır; taşma (overflow) ise OF'dir. İkisi farklı kavramlardır. CF işaretsiz yorumdaki sığmama durumunu, OF işaretli yorumdaki sığmama durumunu bildirir. 7Fh + 01h = 80h toplamasında CF = 0'dır (127 + 1 = 128 bir bayta sığar), ama OF = 1'dir (işaretli +127 + 1 = +128, −128…127 aralığına sığmaz).
7.3.3Kodlama ve eş anlamlılar
8086'da bütün koşullu atlamalar sabit uzunluktadır: 2 bayt. İlk bayt işlem kodu, ikinci bayt 8 bitlik işaretli uzaklıktır (rel8). Bu yüzden bir koşullu atlama, kendisinden sonraki komuta göre en çok 128 bayt geriye ya da 127 bayt ileriye gidebilir. 16 bitlik uzaklıklı koşullu atlamalar (0F 8x işlem kodları) ancak 80386 ile gelmiştir; 8086'da yoktur.
Aynı koşulun birden çok adı olabilir. Örneğin CF = 1 koşulu “elde var” (JC), “aşağıda” (JB, işaretsiz küçük) ve “yukarıda ya da eşit değil” (JNAE) diye üç biçimde okunabilir. Bu adlara eş anlamlı komutlar denir. Assembler hepsini aynı makine koduna çevirir; işlemci hangi adın yazıldığını bilmez. Programcı, koşulun o programdaki anlamına en uygun adı seçer. İki sayının eşitliği sınanıyorsa JE, bir sayacın sıfırlanması sınanıyorsa JZ yazmak kodu okunur kılar.
Yukarıdaki işlem kodu dizisinde bir düzen vardır. Koşullu atlamaların hepsi 70h–7Fh aralığındadır ve ikişer ikişer eşlenir. Her çiftte işlem kodunun en düşük biti koşulu tersine çevirir: 74h JE iken 75h JNE, 72h JB iken 73h JAE, 7Ch JL iken 7Dh JGE. Üst bitler hangi bayrağın ya da bayrak birleşiminin sınanacağını seçer. İşlemcinin koşul devresi önce bu birleşimi hesaplar, sonra en düşük bit 1 ise sonucu tersler. Bu düzen, bir koşulun tersini bulmayı kolaylaştırır: işlem kodunun son bitini değiştirmek yeterlidir. Döngü komutları da E0h–E3h aralığında yan yana durur. Kısa JMP'nin kodu ise EBh'dir.
7.3.4Kod izleme: JNC = JNB = JAE
Eş anlamlı komutların gerçekten aynı makine kodunu ürettiği küçük bir programla görülebilir. Program art arda üç eş anlamlı atlama içerir; üçü de aynı a etiketine gider.
org 100h
jnc a ; 0100h: 73 07
jnb a ; 0102h: 73 05
jae a ; 0104h: 73 03
mov ax, 4 ; 0106h: B8 04 00
a: mov ax, 5 ; 0109h: B8 05 00
ret ; 010Ch: C3Üç atlamanın işlem kodu da 73h'dir; üçü de CF = 0 koşulunu sınar. Yalnız ikinci baytları farklıdır, çünkü her biri farklı adreste durur ve hedefe olan uzaklıkları farklıdır. jnc a 0100h'dedir: sonraki adres 0102h, uzaklık 0109h − 0102h = 7. jnb a 0102h'dedir: uzaklık 0109h − 0104h = 5. jae a 0104h'dedir: uzaklık 0109h − 0106h = 3.
| Adım | Adres | Komut | CF | AX | IP (sonra) | Açıklama |
|---|---|---|---|---|---|---|
| 0 | — | başlangıç | 0 | 0000h | 0100h | .COM başlangıcı |
| 1 | 0100h | jnc a | 0 | 0000h | 0109h | CF = 0 → atlar: 0102h + 07h |
| 2 | 0109h | mov ax, 5 | 0 | 0005h | 010Ch | aradaki üç komut yürütülmedi |
| 3 | 010Ch | ret | 0 | 0005h | 0000h | PSP'ye dönüş |
Program başladığında bütün durum bayrakları 0'dır, CF de 0'dır. İlk komut jnc a bu yüzden atlar ve IP = 0109h olur. jnb, jae ve mov ax, 4 hiç yürütülmez. AX = 0005h olur ve program biter. Başlangıçta CF = 1 olsaydı (örneğin programın başına STC eklenseydi) üç atlama da atlamazdı. İşlemci üç komutu sırayla geçer, AX önce 4, sonra 5 olurdu. İki durumda da son değer aynıdır, ama yürütülen komut sayısı farklıdır: CF = 0 iken 3, CF = 1 iken 6 komut.
7.3.5Atlama kodunu elle hesaplamak
Sunumdaki atlama kodu hesaplayıcısı, bir atlama komutunun adresinden ve hedefinden makine kodunu bulur. Aynı hesap elle üç adımda yapılır. Önce komutun uzunluğu belirlenir: JMP SHORT, bütün Jcc'ler ve LOOP ailesi 2 bayt, JMP NEAR 3 bayttır. Sonra uzaklık hesaplanır: uzaklık = hedef − (adres + uzunluk). Son olarak uzaklık menzile sığıyor mu diye bakılır ve ikiye tümleyenle yazılır.
(a) 0120h'deki JNE 0100h'ye atlayacak. Sonraki IP = 0122h; uzaklık = 0100h − 0122h = −22h. 22h = 0010 0010b; tersi 1101 1101b; artı 1: 1101 1110b = DEh. Kod 75 DE. İşlemci 0122h + FFDEh = 1 0100h → IP = 0100h hesaplar.
(b) 0105h'deki LOOP 0100h'ye dönecek. Uzaklık = 0100h − 0107h = −7 = F9h. Kod E2 F9.
(c) 0100h'deki JE 0190h'ye atlayacak. Uzaklık = 0190h − 0102h = 8Eh = 142. Bu sayı +127'den büyüktür; 8 bitlik işaretli sayı olarak 8Eh, −114 diye okunurdu. Kısa biçim kullanılamaz ve 8086'da koşullu atlamanın uzun biçimi yoktur. Çözüm, koşulu ters çevirip yakın JMP kullanmaktır.
(d) 0100h'deki JMP NEAR 0190h'ye atlayacak. Komut 3 bayt, sonraki IP = 0103h, uzaklık = 008Dh. Kod E9 8D 00 (little-endian: önce düşük bayt).
Kısa biçimde sonuç −128…+127 aralığına sığmıyorsa klasik assembler'lar “relative jump out of range” hatası verir. Bu hata, uzaklığın bir bayta sığmadığını söyler; çözümü komutu değiştirmek ya da kodu yeniden düzenlemektir.
7.4Karşılaştırma: CMP
7.4.1CMP komutu
Bir program çoğunlukla iki değeri karşılaştırıp sonuca göre karar verir: sayı 100'den büyük mü, karakter 'A' mı, sayaç sona geldi mi. Bunun için CMP hedef, kaynak komutu kullanılır. CMP, SUB komutunun yaptığı çıkarmayı yapar: hedef − kaynak. Ancak sonucu hiçbir yere yazmaz. Yalnız bayrakları (CF, ZF, SF, OF, PF, AF) çıkarmanın sonucuna göre ayarlar. İki işlenen de değişmeden kalır.
Karşılaştırma (CMP, compare), hedef − kaynak çıkarmasını yapıp sonucu atan, yalnız bayrakları ayarlayan komuttur. Bayraklar SUB'daki kurallarla ayarlanır. CMP'den sonra gelen koşullu atlama bu bayraklara bakarak karar verir.
CMP'nin işlenen kuralları SUB ile aynıdır. Yazmaç–yazmaç, yazmaç–bellek, bellek–yazmaç ve yazmaç ya da bellek–sabit birleşimleri geçerlidir. İki bellek işleneni olamaz; CMP [SI], [DI] yazılamaz. İki işlenen aynı boyutta olmalıdır. Bellek ile sabit karşılaştırılırken boyut belirtilmelidir: CMP BYTE PTR [SI], 3. Değişken adı kullanılıyorsa boyut değişkenin türünden anlaşılır: cmp dizi[si], 3 bir bayt karşılaştırmasıdır, çünkü dizi DB ile tanımlıdır.
CMP'nin yürütme süresi işlenenlere göre değişir. Yazmaç–yazmaç 3, yazmaç–sabit 4 saat turu sürer. Bellek işleneni varsa önce etkin adres hesaplanır ve bir yol döngüsüyle bellek okunur: yazmaç–bellek 9 + EA, bellek–sabit 10 + EA tur. Karşılaştırma ile ardındaki koşullu atlama birlikte bir kararın bedelidir: CMP AL, BL + atlanmayan JE 3 + 4 = 7 tur, atlanan JE 3 + 16 = 19 tur.
| Komut | Çıkarma | ZF | CF | SF | OF | JE / JZ |
|---|---|---|---|---|---|---|
CMP 05h, 02h | 5 − 2 = 3 → 03h | 0 | 0 | 0 | 0 | atlamaz |
CMP 07h, 07h | 7 − 7 = 0 → 00h | 1 | 0 | 0 | 0 | atlar |
Tablodaki yazım kavramsaldır; 8086'da iki sabiti karşılaştıran komut yoktur, işlenenlerden biri yazmaç ya da bellek olmalıdır. Birinci örnekte fark sıfır değildir ve ZF = 0 olur. İkinci örnekte fark sıfırdır: ZF = 1 olur ve JE ya da JZ atlar. Eşitlik denetiminin tamamı bu kadardır: iki sayı eşitse farkları sıfırdır.
7.4.2CMP bayrakları adım adım
Eşitlik yalnız ZF'ye bakar. “Büyük” ve “küçük” kararları ise sayıların nasıl yorumlandığına bağlıdır. Aynı sekiz bit işaretsiz bir sayı olarak 0–255, işaretli bir sayı olarak −128…127 aralığında bir değer taşır. CMP bu ayrımı yapmaz; bitleri çıkarır ve her iki yorum için gereken bilgiyi bayraklara yazar. İşaretsiz yorumun cevabı CF'de ve ZF'de, işaretli yorumun cevabı SF'de, OF'de ve ZF'de durur. Tablo 7.8, CMP AL, BL komutunun beş farklı girişte bayrakları nasıl ayarladığını gösterir.
| AL | BL | AL − BL | CF | ZF | SF | OF | PF | AF | İşaretsiz | İşaretli |
|---|---|---|---|---|---|---|---|---|---|---|
| 05h (5) | 02h (2) | 03h | 0 | 0 | 0 | 0 | 1 | 0 | 5 > 2: JA | 5 > 2: JG |
| 07h (7) | 07h (7) | 00h | 0 | 1 | 0 | 0 | 1 | 0 | eşit: JE | eşit: JE |
| 05h (5) | 07h (7) | FEh | 1 | 0 | 1 | 0 | 0 | 1 | 5 < 7: JB | 5 < 7: JL |
| 7Fh (127) | 80h (128 / −128) | FFh | 1 | 0 | 1 | 1 | 1 | 0 | 127 < 128: JB | 127 > −128: JG |
| FBh (251 / −5) | 03h (3) | F8h | 0 | 0 | 1 | 0 | 0 | 0 | 251 > 3: JA | −5 < 3: JL |
Birinci satırda fark pozitiftir ve hiçbir bayrak kalkmaz; yalnız PF = 1 olur, çünkü 03h = 0000 0011b iki tane 1 içerir. İkinci satırda fark sıfırdır ve ZF = 1 olur. Hem işaretli hem işaretsiz bakışta sayılar eşittir.
Üçüncü satırda küçük sayıdan büyük sayı çıkarılır. 5 − 7 işlemi bir ödünç gerektirir; CF = 1 olur. Sonuç FEh'dir: işaretsiz 254, işaretli −2. Sonucun en anlamlı biti 1 olduğu için SF = 1'dir. −2 sekiz bite sığdığı için taşma yoktur: OF = 0. Düşük dörtlükte de 5 − 7 için ödünç alındığından AF = 1'dir. FEh = 1111 1110b yedi tane 1 içerir; PF = 0'dır.
Dördüncü ve beşinci satırlar asıl ilginç durumlardır. Bu iki satırda işaretli ve işaretsiz yorum farklı cevap verir (Şekil 7.6 ve 7.7).
CMP 7Fh, 80h: işaretsiz bakışta 127 < 128 (CF = 1, JB atlar); işaretli bakışta 127 − (−128) = 255 sekiz bite sığmaz, OF = 1 olur ve SF = OF olduğundan JG atlar.7Fh − 80h işleminde işaretsiz yorum 127 − 128'dir; ödünç alınır ve CF = 1 olur. İşaretli yorum ise 127 − (−128) = +255'tir. Bu değer −128…127 aralığına sığmaz; OF = 1 olur. Sekiz bitlik sonuç FFh'dir ve en anlamlı biti 1 olduğundan SF = 1'dir. SF tek başına “sonuç negatif, yani AL küçük” derdi; bu yanlıştır. Doğru karar SF ile OF birlikte okunarak verilir.
CMP FBh, 03h: FBh işaretsiz 251, işaretli −5'tir. İşaretsiz bakışta 251 > 3 (CF = 0, JA atlar); işaretli bakışta −5 < 3 (SF ≠ OF, JL atlar). Aynı bitler farklı karar verir.FBh − 03h = F8h işleminde ödünç yoktur; CF = 0 olur. İşaretsiz yorumda 251 > 3'tür. İşaretli yorumda −5 − 3 = −8'dir. −8 sekiz bite sığar (F8h), taşma yoktur: OF = 0. Sonuç negatiftir: SF = 1. SF ≠ OF olduğundan işaretli yorumda AL küçüktür. Aynı CMP, verinin türüne göre iki zıt karar verir. Hangi kararın doğru olduğunu işlemci bilemez; bunu programcı, uygun atlama komutunu seçerek belirtir.
7.4.3İşaretli sayılar için atlamalar
İşaretli karşılaştırma için kullanılan atlamalar “büyük” ve “küçük” sözcüklerinin İngilizcesiyle adlandırılır: Greater (büyük) ve Less (küçük). Bu adlar, sayıların ikiye tümleyen gösterimde işaretli olduğu varsayımıyla kullanılır. Tablo 7.9 bu aileyi koşullarıyla verir.
| Komut | Anlamı | Koşul | Zıt komut |
|---|---|---|---|
JE, JZ | A = B | ZF = 1 | JNE, JNZ |
JNE, JNZ | A ≠ B | ZF = 0 | JE, JZ |
JG, JNLE | A > B (greater) | ZF = 0 ve SF = OF | JNG, JLE |
JL, JNGE | A < B (less) | SF ≠ OF | JNL, JGE |
JGE, JNL | A ≥ B | SF = OF | JNGE, JL |
JLE, JNG | A ≤ B | ZF = 1 veya SF ≠ OF | JNLE, JG |
Koşullar bir bakışta karışık görünür, ama tek bir ilkeden türer: işaretli yorumda “A < B” bilgisi SF ≠ OF ile verilir. Geri kalanlar buradan çıkar. A ≥ B, A < B'nin değilidir: SF = OF. A > B, “A ≥ B ve A ≠ B” demektir: SF = OF ve ZF = 0. A ≤ B, A > B'nin değilidir: ZF = 1 veya SF ≠ OF. Eş anlamlılar da aynı mantıkla kurulur. JG, “küçük ya da eşit değil” anlamında JNLE diye de yazılır.
7.4.4Neden SF = OF?
CMP A, B işlemcide A − B olarak hesaplanır. Taşma olmazsa sonucun işareti doğrudur: A < B ise fark negatiftir ve SF = 1 olur. Taşma varsa (OF = 1) fark sekiz ya da on altı bite sığmamıştır. Sığmayan bir sonucun işaret biti ters çıkar. Bunun nedeni işaretli sayıların bir çember üzerinde dizilmesidir (Şekil 7.8): aralığın bir ucunu aşan değer öbür uçtan girer. 127'ye 1 eklenince −128 olur; −128'den 1 çıkarılınca 127 olur.
Gerçek işaret bu yüzden SF ile OF'nin dışlayıcı veyasıdır: gerçek işaret = SF XOR OF. Taşma yoksa (OF = 0) gerçek işaret SF'nin kendisidir. Taşma varsa (OF = 1) gerçek işaret SF'nin tersidir. SF ≠ OF olduğunda XOR 1 verir: gerçek fark negatiftir ve A < B'dir. SF = OF olduğunda XOR 0 verir: gerçek fark negatif değildir ve A ≥ B'dir.
CMP A, B komutundan sonra, A ve B ikiye tümleyen gösterimde işaretli sayılar olarak yorumlanırsa A < B ⇔ SF ≠ OF ve A ≥ B ⇔ SF = OF'dir.
Gerekçe. Gerçek fark d = A − B, sekiz bitte −255…+255 aralığındadır. d −128…127 aralığındaysa sonuç doğru gösterilir, OF = 0 ve SF = (d < 0) olur. d > 127 ise sonuç d − 256 olarak görünür, negatiftir: SF = 1, OF = 1; gerçek fark pozitiftir. d < −128 ise sonuç d + 256 olarak görünür, pozitif ya da sıfırdır: SF = 0, OF = 1; gerçek fark negatiftir. Üç durumda da (d < 0) = SF XOR OF'dir.
| A | B | A − B (8 bit) | Gerçek fark | SF | OF | Karar |
|---|---|---|---|---|---|---|
| 5 | 7 | FEh (−2) | −2 | 1 | 0 | SF ≠ OF → 5 < 7 |
| 127 (7Fh) | −128 (80h) | FFh (−1) | +255, sığmaz | 1 | 1 | SF = OF → 127 > −128 |
| −128 (80h) | 1 | 7Fh (+127) | −129, sığmaz | 0 | 1 | SF ≠ OF → −128 < 1 |
| −5 (FBh) | 3 | F8h (−8) | −8 | 1 | 0 | SF ≠ OF → −5 < 3 |
Tablonun ikinci ve üçüncü satırları taşmalıdır. İkisinde de SF tek başına yanlış cevap verirdi: ikinci satırda “negatif, A küçük” derdi, üçüncü satırda “pozitif, A büyük” derdi. OF ile birlikte değerlendirildiğinde karar doğrudur. Donanım açısından bu iş çok ucuzdur: JL'nin koşul devresi tek bir XOR kapısıdır. JLE için bu XOR'un çıkışı ZF ile VEYA'lanır.
İşaretli karşılaştırmadan sonra JS ile “küçükse atla” yazmak. JS yalnız SF'ye bakar ve taşma olduğunda yanlış karar verir: MOV AL, 80h / CMP AL, 1 sonrasında SF = 0'dır ve JS atlamaz, oysa −128 < 1'dir. Doğru komut JL'dir; SF ≠ OF (0 ≠ 1) olduğundan atlar.
7.4.5İşaretsiz sayılar için atlamalar
İşaretsiz karşılaştırma için kullanılan atlamalar “yukarıda” ve “aşağıda” sözcüklerinin İngilizcesiyle adlandırılır: Above ve Below. İşaretsiz yorumda karar çok daha basittir, çünkü taşma kavramı yoktur. A − B çıkarmasında A < B ise ödünç alınır ve CF = 1 olur. Demek ki “aşağıda” testi doğrudan elde bayrağıdır: JB ≡ JC.
| Komut | Anlamı | Koşul | Zıt komut |
|---|---|---|---|
JE, JZ | A = B | ZF = 1 | JNE, JNZ |
JNE, JNZ | A ≠ B | ZF = 0 | JE, JZ |
JA, JNBE | A > B (above) | CF = 0 ve ZF = 0 | JNA, JBE |
JB, JNAE, JC | A < B (below) | CF = 1 | JNB, JAE, JNC |
JAE, JNB, JNC | A ≥ B | CF = 0 | JNAE, JB, JC |
JBE, JNA | A ≤ B | CF = 1 veya ZF = 1 | JNBE, JA |
JA'nın koşulu “ödünç yok ve fark sıfır değil” demektir; A hem B'den küçük değildir hem de ona eşit değildir. JBE bunun tersidir. JAE yalnız CF = 0'a bakar; eşitlikte de ödünç alınmadığı için “büyük ya da eşit” kendiliğinden karşılanır.
7.4.6Above/Below mı, Greater/Less mi?
Altı karşılaştırma ilişkisinin her biri için iki komut vardır: biri işaretsiz, biri işaretli. Eşitlik ve eşitsizlikte iki aile aynı komutu kullanır, çünkü eşitlik bitlerin aynı olmasıdır ve yoruma bağlı değildir. Tablo 7.12 bu eşlemeyi tek bakışta verir.
| Karşılaştırma | İşaretsiz (A/B) | İşaretli (G/L) |
|---|---|---|
| A = B | JE / JZ | JE / JZ |
| A ≠ B | JNE / JNZ | JNE / JNZ |
| A > B | JA / JNBE | JG / JNLE |
| A ≥ B | JAE / JNB | JGE / JNL |
| A < B | JB / JNAE | JL / JNGE |
| A ≤ B | JBE / JNA | JLE / JNG |
Seçimi verinin türü belirler. Adresler, sayaçlar, dizi indisleri, uzunluklar ve ASCII kodları işaretsizdir; bunlarla A/B ailesi kullanılır. Sıcaklık, iki ölçüm arasındaki fark, bir konum değişimi gibi negatif olabilen değerler işaretlidir; bunlarla G/L ailesi kullanılır. Kolay bir hatırlatıcı şudur: Above / Below işaretsiz, Greater / Less işaretli. Değerler 0–127 aralığında kaldığı sürece iki aile aynı sonucu verir; fark ancak en anlamlı biti 1 olan değerlerde ortaya çıkar. Bu yüzden yanlış aile seçimi küçük deneme verileriyle çoğu zaman fark edilmez.
7.4.7Kod izleme: aynı bitler, JA ve JG
Aşağıdaki program aynı CMP'nin ardından JA ile JG'nin farklı karar verdiğini gösterir. Programın ikinci yarısı da taşmalı bir karşılaştırmada JG'nin doğru çalıştığını sınar. CL, CH ve DL yazmaçları işaret olarak kullanılır: bir atlama atlamazsa ilgili yazmaç 1 olur.
MOV AL, 0FBh ; 251 ya da -5
MOV BL, 03h
CMP AL, BL ; FBh - 03h
JA a1 ; işaretsiz: 251 > 3 ?
MOV CL, 1 ; JA atlamasaydı
a1: JG g1 ; işaretli: -5 > 3 ?
MOV CH, 1 ; JG atlamadı
g1: MOV AL, 7Fh ; 127
CMP AL, 80h ; 127 - (-128)
JG g2 ; SF = OF ?
MOV DL, 1 ; JG atlamasaydı
g2: RETKaynakta 0FBh yazımına dikkat edilmelidir. Onaltılık sayı bir harfle başlıyorsa önüne 0 konur. FBh yazılırsa assembler onu bir ad sanar ve hata verir.
| Adım | Adres | Komut | Kod | AL | CF ZF SF OF | Sonuç |
|---|---|---|---|---|---|---|
| 1 | 0100h | MOV AL, 0FBh | B0 FB | FBh | 0 0 0 0 | — |
| 2 | 0102h | MOV BL, 03h | B3 03 | FBh | 0 0 0 0 | BL = 03h |
| 3 | 0104h | CMP AL, BL | 3A C3 | FBh | 0 0 1 0 | F8h, saklanmaz |
| 4 | 0106h | JA a1 | 77 02 | FBh | 0 0 1 0 | atlar → 010Ah |
| 5 | 010Ah | JG g1 | 7F 02 | FBh | 0 0 1 0 | atlamaz → 010Ch |
| 6 | 010Ch | MOV CH, 1 | B5 01 | FBh | 0 0 1 0 | CX = 0100h |
| 7 | 010Eh | MOV AL, 7Fh | B0 7F | 7Fh | 0 0 1 0 | — |
| 8 | 0110h | CMP AL, 80h | 3C 80 | 7Fh | 1 0 1 1 | FFh, saklanmaz |
| 9 | 0112h | JG g2 | 7F 02 | 7Fh | 1 0 1 1 | atlar → 0116h |
| 10 | 0116h | RET | C3 | 7Fh | 1 0 1 1 | IP = 0000h |
Üçüncü adımda CMP, FBh − 03h = F8h hesaplar. Ödünç yoktur (CF = 0), sonuç sıfır değildir (ZF = 0), en anlamlı bit 1'dir (SF = 1), taşma yoktur (OF = 0). Dördüncü adımda JA CF = 0 ve ZF = 0 koşulunu sağlanmış bulur ve atlar; işaretsiz yorumda 251 > 3'tür. MOV CL, 1 satırı atlanır ve CL = 0 kalır. Beşinci adımda JG aynı bayraklara bakar. ZF = 0'dır ama SF = 1, OF = 0'dır; SF ≠ OF olduğundan atlamaz. İşaretli yorumda −5 > 3 yanlıştır. Altıncı adımda CH = 1 olur ve CX = 0100h'dir. Aynı CMP iki farklı karar vermiştir.
Programın ikinci yarısında AL = 7Fh = 127 yapılır ve 80h ile karşılaştırılır. 7Fh − 80h = FFh'dir. İşaretsiz yorumda 127 < 128'dir ve ödünç alınır: CF = 1. İşaretli yorumda 127 − (−128) = 255 sığmaz: OF = 1. Sonucun işaret biti SF = 1'dir. PF = 1'dir, çünkü FFh sekiz tane 1 içerir. Dokuzuncu adımda JG, ZF = 0 ve SF = OF (1 = 1) koşulunu sağlanmış bulur ve atlar. 127 > −128 doğrudur. SF tek başına “küçük” derdi. DL = 0 kalır. Program bitince CL = 0, CH = 1, DL = 0'dır: işaretli yorumda −5, 3'ten büyük değildir; 127 ise −128'den büyüktür.
CMP AL, 80h sonrasında CF = 1, SF = 1, OF = 1. ZF = 0 ve SF = OF olduğundan JG atlar.7.4.8Kod izleme: CMP + JE ve CMP + JA
Gerçek programlarda karşılaştırmanın iki sonucu için iki ayrı blok bulunur. Aşağıdaki iki kısa parça bu kalıbın en basit hâlidir. Parçalar RET içermez, çünkü bir programın içine yerleştirilmek üzere yazılmıştır. Bloklardaki işlemler yerine yalnız açıklama yazılıdır.
mov ax, 5
mov bx, 5
cmp ax, bx
je equal_message
jmp not_equal_message
equal_message: ; eşitse
jmp end_program
not_equal_message: ; eşit değilse
end_program:Assembler bu parçayı 0100h'den yerleştirdiğinde cmp ax, bx 0106h'de (3B C3), je 0108h'de (74 02), jmp not_equal_message 010Ah'de (EB 02), jmp end_program 010Ch'de (EB 00) durur. İzleme kısadır:
mov ax, 5vemov bx, 5: AX = BX = 0005h.cmp ax, bx: 5 − 5 = 0 → ZF = 1, CF = 0, SF = 0, OF = 0. PF = 1 olur (00h'de sıfır tane 1 vardır ve sıfır çifttir). AX değişmez.je equal_message: ZF = 1 olduğundan atlar; IP = 010Ch.equal_messagebloğundaki işlemler yapılır, ardındanjmp end_programöbür bloğun üzerinden atlar.
Son adımdaki JMP kritik önemdedir. Assembly'de bloklar yalnız etiketlerle ayrılır; bir bloğun bittiğini işlemciye söyleyen bir yapı yoktur. jmp end_program olmasaydı işlemci eşitlik bloğunu bitirince sıradaki satıra, yani eşitsizlik bloğuna geçerdi ve iki blok da yürütülürdü. Bu örnekte iki blok boş olduğundan jmp end_program kendinden hemen sonraki adrese gider ve kodu EB 00 olur: uzaklık sıfırdır. Bloklar dolu olduğunda uzaklık blok uzunluğu kadar büyür. Sayılar eşit olmasaydı je atlamazdı; jmp not_equal_message akışı eşitsizlik bloğuna götürürdü.
İkinci parça aynı kalıbı işaretsiz “büyük mü” sorusuyla kurar:
mov ax, 5
mov bx, 7
cmp ax, bx
ja jump_above
jmp not_jump_above
jump_above: ; ax büyükse yapılacaklar
jmp end_program
not_jump_above: ; ax küçük veya eşitse
end_program:Burada cmp ax, bx 5 − 7 = FFFEh hesaplar; işaretli yorumda −2'dir. Ödünç alındığından CF = 1, sonuç sıfır olmadığından ZF = 0, en anlamlı bit 1 olduğundan SF = 1'dir. Taşma yoktur: OF = 0. Düşük dörtlükte 5 − 7 için ödünç alındığından AF = 1 olur. ja için CF = 0 ve ZF = 0 gerekir; CF = 1 olduğundan atlamaz. jmp not_jump_above akışı “küçük veya eşit” bloğuna götürür. Aynı kodda ja yerine jg yazılsaydı da sonuç değişmezdi: SF = 1, OF = 0, SF ≠ OF olduğundan JG de atlamazdı. İki değer de pozitif ve küçük olduğu için iki aile aynı cevabı verir.
7.4.9Kod izleme: karşılaştır ve yaz
Aşağıdaki program iki sayıyı karşılaştırır. Sayılar eşitse ekrana y, değilse n yazar. Karakteri DOS'un INT 21h hizmetiyle yazar: AH = 02h iken DOS, DL'deki karakteri ekrana basar. Programın yapısı önemlidir: iki dal yalnız DL'ye farklı bir değer koyar ve sonra ortak yaz etiketinde birleşir. Yazma kodu iki kez yazılmaz.
org 100h
mov al, 25 ; al = 25
mov bl, 10 ; bl = 10
cmp al, bl ; al - bl karşılaştır
je equal ; al = bl ise (zf = 1) atla
mov dl, 'n' ; al <> bl: 'n' yazılacak
jmp yaz ; ortak yazma satırına git
equal:
mov dl, 'y' ; al = bl: 'y' yazılacak
yaz:
mov ah, 2 ; DOS: DL'deki karakteri yaz
int 21h
ret ; DOS'a dön| Adım | Adres | Komut | Kod | AX | DL | ZF CF | Ekran |
|---|---|---|---|---|---|---|---|
| 1 | 0100h | mov al, 25 | B0 19 | 0019h | 00h | 0 0 | |
| 2 | 0102h | mov bl, 10 | B3 0A | 0019h | 00h | 0 0 | |
| 3 | 0104h | cmp al, bl | 3A C3 | 0019h | 00h | 0 0 | |
| 4 | 0106h | je equal | 74 04 | 0019h | 00h | 0 0 | |
| 5 | 0108h | mov dl, 'n' | B2 6E | 0019h | 6Eh | 0 0 | |
| 6 | 010Ah | jmp yaz | EB 02 | 0019h | 6Eh | 0 0 | |
| 7 | 010Eh | mov ah, 2 | B4 02 | 0219h | 6Eh | 0 0 | |
| 8 | 0110h | int 21h | CD 21 | 026Eh | 6Eh | 0 0 | n |
| 9 | 0112h | ret | C3 | 026Eh | 6Eh | 0 0 | n |
AL = 25 = 19h ve BL = 10 = 0Ah yüklenir. cmp al, bl 25 − 10 = 15 = 0Fh hesaplar; ZF = 0 olur. Düşük dörtlükte 9 − A için ödünç alındığından AF = 1, 0Fh dört tane 1 içerdiğinden PF = 1'dir. je equal atlamaz. mov dl, 'n' DL'ye 'n' karakterinin ASCII kodunu, 6Eh'yi koyar. jmp yaz 'y' satırını atlar; uzaklığı 2'dir, çünkü atlanan mov dl, 'y' iki bayttır. İki dal yaz etiketinde birleşir.
mov ah, 2 AH'yi 02h yapar; AX = 0219h olur. int 21h ekrana n yazar. DOS bu hizmetten dönerken yazdığı karakteri AL'ye de koyar: AL = 6Eh, AX = 026Eh. Programın sonundaki ret zorunludur. O olmasaydı işlemci int 21h'den sonraki baytları komut sanıp yürütmeye devam ederdi; bu baytlar ne olduğu belli olmayan bellek içeriğidir.
Programda mov dl, 'y' hiç yürütülmez, ama yine bellekte yer kaplar. Sayılar eşit olsaydı (örneğin mov bl, 25) je equal atlar, DL = 79h ('y') olur, jmp yaz ise hiç yürütülmezdi. Karşılaştırma bloklarında her zaman bir dal “sıradan düşen” daldır, öbürü atlamayla gidilen daldır.
7.5Yüksek düzey yapıların karşılıkları
7.5.1if/else kalıbı
Yüksek düzeyli bir dildeki if (koşul) A else B yapısı assembly'de iki atlamayla kurulur. Akla ilk gelen yol koşulun kendisiyle atlamaktır: koşul doğruysa A bloğuna git. Ama bu yolda kodun sırası ters döner; koşul sağlanmadığında yürütülecek B bloğu, atlama komutunun hemen altına yazılmak zorunda kalır. Daha düzenli olan yol koşulun tersiyle atlamaktır. Koşul sağlanmıyorsa A bloğunun üzerinden B bloğuna atlanır. Koşul sağlanıyorsa atlama yapılmaz ve işlemci sıradan düşerek A bloğuna girer. Böylece assembly kodu, yüksek düzeyli koddaki A–B sırasını korur.
if (ax == bx) {
; A bloğu
} else {
; B bloğu
} cmp ax, bx
jne degil ; koşulun TERSİ
; A bloğu
jmp son
degil:
; B bloğu
son:Kalıbın iki parçası vardır. Birincisi koşulun tersiyle yapılan atlamadır: == için JNE, < için JGE ya da JAE, > için JLE ya da JBE. İkincisi A bloğunun sonundaki jmp son'dur. Bu atlama unutulursa A bloğu bittiğinde işlemci B bloğuna da girer. Else kısmı olmayan bir if yapısında ikinci atlamaya gerek yoktur; ters koşulla doğrudan blok sonuna atlanır.
| Yüksek düzey koşul | İşaretsiz veride atla | İşaretli veride atla |
|---|---|---|
a == b | JNE | JNE |
a != b | JE | JE |
a < b | JAE | JGE |
a <= b | JA | JG |
a > b | JBE | JLE |
a >= b | JB | JL |
Bileşik koşullar da aynı yolla kurulur. if (a >= -10 && a <= 10) gibi bir VE koşulunda her alt koşul ayrı bir CMP ve ters atlamayla sınanır. Alt koşullardan biri sağlanmadığında doğrudan blok dışına atlanır. VEYA koşulunda ise alt koşullardan biri sağlandığında doğrudan bloğun içine atlanır. Bu, yüksek düzeyli dillerdeki “kısa devre” değerlendirmesinin makine düzeyindeki karşılığıdır: sonucu belli olan bir koşulun geri kalanı hiç sınanmaz.
7.5.2while, do-while ve for kalıpları
Döngü, bir atlamanın geriye doğru yapılmasıdır. while (koşul) gövde yapısında koşul gövdeden önce sınanır; koşul baştan yanlışsa gövde hiç çalışmaz. Assembly karşılığında döngünün başına bir etiket konur, koşul ters atlamayla sınanır, gövdenin sonunda başa dönülür:
while (cx > 0) { ; işaretsiz
; gövde
}bas: cmp cx, 0
jbe bitti ; cx <= 0 ise çık
; gövde
jmp bas
bitti:do { gövde } while (koşul) yapısında koşul gövdeden sonra sınanır; gövde en az bir kez çalışır. Karşılığı daha kısadır, çünkü tek bir koşullu atlama hem sınamayı hem geri dönüşü yapar:
bas: ; gövde
cmp cx, 0
ja bas ; koşul doğruysa başa dönBu ikinci biçim her turda bir atlama daha az yürütür. while biçiminde her tur bir koşullu atlama (atlamaz, 4 tur) ve bir JMP (15 tur) içerir. do-while biçiminde her tur yalnız bir koşullu atlama (atlar, 16 tur) içerir. Bu yüzden derleyiciler while döngüsünü çoğunlukla “önce bir kez sına, sonra do-while olarak dön” biçimine çevirir.
for (i = n; i > 0; i--) gibi bir sayaçlı döngü, 8086'da en doğal biçimde CX ve LOOP komutuyla yazılır. LOOP, CX'i azaltma, sıfırla karşılaştırma ve geri atlama işlerini tek komutta yapar. Sayaç yukarı doğru sayılıyorsa (for (i = 0; i < n; i++)) sayaç başka bir yazmaçta tutulur. Her turda INC ile artırılır; CMP ve ardından JB ya da JNE ile sınanır.
7.5.3Kod izleme: if/else ve while
Aşağıdaki program iki kalıbı arka arkaya kullanır. Önce if (AX == BX) DX = 1; else DX = 2; yapısı yürütülür. Ardından CX = 2 ile while (CX > 0) { DX += CX; CX--; } döngüsü çalışır.
MOV AX, 12
MOV BX, 30
CMP AX, BX
JNE degil ; koşulun TERSİ
MOV DX, 1 ; A bloğu: AX = BX
JMP son
degil: MOV DX, 2 ; B bloğu: AX ≠ BX
son: MOV CX, 2 ; while (CX > 0)
bas: CMP CX, 0
JBE bitti ; CX ≤ 0 → çık
ADD DX, CX ; gövde
DEC CX
JMP bas
bitti: RET| Adım | Adres | Komut | Kod | CX | DX | ZF CF | Karar |
|---|---|---|---|---|---|---|---|
| 1–2 | 0100h | MOV AX, 12 / MOV BX, 30 | B8 0C 00 / BB 1E 00 | 0000h | 0000h | 0 0 | — |
| 3 | 0106h | CMP AX, BX | 3B C3 | 0000h | 0000h | 0 1 | 12 − 30: eşit değil, ödünç |
| 4 | 0108h | JNE degil | 75 05 | 0000h | 0000h | 0 1 | atlar → 010Fh |
| 5 | 010Fh | MOV DX, 2 | BA 02 00 | 0000h | 0002h | 0 1 | B bloğu |
| 6 | 0112h | MOV CX, 2 | B9 02 00 | 0002h | 0002h | 0 1 | — |
| 7–8 | 0115h | CMP CX, 0 / JBE bitti | 83 F9 00 / 76 05 | 0002h | 0002h | 0 0 | 2 > 0: atlamaz |
| 9–10 | 011Ah | ADD DX, CX / DEC CX | 03 D1 / 49 | 0001h | 0004h | 0 0 | gövde, tur 1 |
| 11–13 | 011Dh | JMP bas / CMP / JBE | EB F6 | 0001h | 0004h | 0 0 | 1 > 0: atlamaz |
| 14–15 | 011Ah | ADD DX, CX / DEC CX | — | 0000h | 0005h | 1 0 | gövde, tur 2 |
| 16–18 | 011Dh | JMP bas / CMP / JBE | — | 0000h | 0005h | 1 0 | 0 = 0: atlar → 011Fh |
| 19 | 011Fh | RET | C3 | 0000h | 0005h | 1 0 | IP = 0000h |
Üçüncü adımda CMP AX, BX 12 − 30 = −18 hesaplar; 16 bitlik sonuç FFEEh'dir. Sonuç sıfır değildir (ZF = 0), ödünç alınmıştır (CF = 1), işaret biti 1'dir (SF = 1). Dördüncü adımda JNE degil koşulun tersini sağlanmış bulur ve A bloğunun üzerinden atlar. MOV DX, 1 ve JMP son yürütülmez. Beşinci adımda B bloğu DX = 2 yapar. B bloğu son bloktur ve doğrudan son etiketine düşer; bu yolda JMP gerekmez.
Altıncı adımda CX = 2 olur ve döngü başlar. CMP CX, 0 2 − 0 = 2 hesaplar: CF = 0, ZF = 0. JBE bitti için CF = 1 ya da ZF = 1 gerekir; atlamaz ve gövdeye girilir. Gövde DX = 2 + 2 = 4, CX = 1 yapar. JMP bas başa döner ve koşul yeniden sınanır: 1 > 0, atlamaz. İkinci turda DX = 4 + 1 = 5, CX = 0 olur. Bu turda DEC CX sonucu sıfırladığı için ZF = 1'dir, ama karar yine başta verilir. CMP CX, 0 0 − 0 = 0 hesaplar: ZF = 1, CF = 0. JBE bitti atlar. Sonuç DX = 5'tir: B bloğunun 2'si, artı 2, artı 1.
CMP CX, 0 komutunun kodu 83 F9 00'dır. 83h işlem kodu, 16 bitlik bir işleneni işaret genişletilmiş 8 bitlik bir sabitle karşılaştırır; böylece sabit 2 yerine 1 bayt tutar. Döngünün sonundaki JMP bas 011Dh'dedir ve iki bayttır: uzaklık 0115h − 011Fh = −0Ah = F6h, kod EB F6.
Bu döngü bir while döngüsüdür; koşul gövdeden önce sınanır. CX = 0 ile başlansaydı gövde hiç çalışmazdı. LOOP komutu ise koşulu gövdeden sonra sınar; iki yapı arasındaki önemli fark budur.
Tablo 7.16'daki ilk satırlarda CX = 0000h gösterilmiştir. Sunumdaki izleme de bu varsayımla yapılmıştır. Gerçekte emu8086, bir .COM programını yüklerken CX'e programın bayt sayısını yükler. Bu program 32 bayttır (0100h–011Fh), dolayısıyla emu8086'da program CX = 0020h ile başlar. Sonuç etkilenmez, çünkü CX ilk kez okunmadan önce MOV CX, 2 ile yazılır. Genel kural şudur: başlangıçtaki yazmaç değerlerine güvenilmez; kullanılacak her yazmaç önce bir değerle yüklenir.
7.5.4Menzil dışı koşullu atlama
8086'da koşullu atlamalar yalnız kısa biçimdedir: sonraki komuta göre −128…+127 bayt. Uzun bir if bloğunun üzerinden atlamak ya da uzun bir döngünün başına dönmek gerektiğinde bu menzil yetmeyebilir (Şekil 7.10).
Çözüm, koşulu ters çevirip kısa bir atlamayla hemen sonraki komutun üzerinden geçmek, uzak hedefe ise yakın JMP ile gitmektir:
; hatalı: hedef çok uzak
cmp al, 0
je cok_uzak ; out of range!
; ... 200 baytlık kod ...
cok_uzak:; doğru: ters koşul + yakın JMP
cmp al, 0
jne devam ; ters koşul, kısa
jmp cok_uzak ; near JMP (E9)
devam:
; ... 200 baytlık kod ...
cok_uzak:İkinci yazımda jne devam yalnız 3 bayt ileri atlar ve kısa menzile rahatça sığar. AL = 0 ise JNE atlamaz ve işlemci yakın JMP'ye düşer; o da kesim içinde her yere ulaşır. AL ≠ 0 ise JNE, JMP'nin üzerinden devam etiketine geçer. Davranış ilk yazımla aynıdır, yalnız komut çifti 2 yerine 5 bayttır.
Bu dönüşüm her koşullu atlamaya uygulanabilir, çünkü her koşulun bir tersi vardır ve işlem kodunun son biti değiştirilerek bulunur. Klasik assembler'lar (örneğin 8086 kipindeki MASM) menzil dışı bir Jcc görünce “relative jump out of range” hatası verir ve dönüşümü programcıya bırakır. emu8086'nın assembler'ı ise aynı dönüşümü kendisi yapar ve bir uyarıyla bildirir. Örneğin 0102h'deki je uzak, hedef 01CFh'de iken 75 03 E9 C8 00 olarak kodlanır: JNE +3 (JMP'nin üç baytının üzerinden geçiş) ve JMP 01CFh (0107h + 00C8h = 01CFh). Hangi assembler kullanılırsa kullanılsın makine düzeyinde olan aynıdır. Programın uzunluğu da bu yüzden kaynakta göründüğünden 3 bayt fazla olur.
Menzil hatasını JE yerine eş anlamlısı JZ yazarak ya da JE SHORT ile düzeltmeye çalışmak. Eş anlamlı komut aynı makine koduna çevrilir ve menzili aynıdır. SHORT ise menzili büyütmez, yalnız kısa biçimi zorlar. LOOPE gibi bir döngü komutu da çözüm değildir: o da 2 bayt ve rel8'dir, üstelik CX'i değiştirir.
7.6Döngü komutları
7.6.1LOOP ailesi
Sayaçlı döngüler o kadar sık yazılır ki 8086 bunlar için ayrı komutlar sunar. Bu komutların hepsi sayaç olarak CX yazmacını kullanır. CX'in “sayaç yazmacı” (count register) diye anılmasının nedeni budur. Tablo 7.17 aileyi özetler.
| Komut | Kod | İşlem ve atlama koşulu |
|---|---|---|
LOOP | E2 | CX'i 1 azalt; CX ≠ 0 ise etikete atla. |
LOOPE | E1 | CX'i 1 azalt; CX ≠ 0 ve eşitse (ZF = 1) atla. |
LOOPZ | E1 | LOOPE ile aynı: CX ≠ 0 ve ZF = 1 ise atla. |
LOOPNE | E0 | CX'i 1 azalt; CX ≠ 0 ve eşit değilse (ZF = 0) atla. |
LOOPNZ | E0 | LOOPNE ile aynı: CX ≠ 0 ve ZF = 0 ise atla. |
JCXZ | E3 | CX = 0 ise etikete atla (CX azaltılmaz). |
Aile üç ortak özellik taşır. Birincisi, sayaç her zaman CX'tir; başka yazmaç seçilemez. İkincisi, LOOP ailesi CX'i azaltırken bayrakları değiştirmez. CX sıfıra inse bile ZF kalkmaz. Bu, aynı işi yapan DEC CX komutundan önemli bir farktır. Üçüncüsü, hepsi 2 bayttır ve rel8 kullanır; döngü gövdesi en çok 128 bayt geriye uzanabilir. Gövde daha uzunsa döngü DEC CX, JZ ve yakın JMP ile yazılır.
7.6.2LOOP nasıl çalışır?
LOOP etiket komutunda işlem sırası önemlidir: önce CX azaltılır, sonra sıfır olup olmadığına bakılır. Bu yüzden LOOP bir do-while döngüsü kurar. Gövde, LOOP'a ulaşmadan önce en az bir kez yürütülür. CX = n ile başlayan bir döngüde gövde tam n kez çalışır: CX n − 1, n − 2, …, 1 değerlerinde geri atlanır, 0'da döngüden çıkılır.
mov cx, n
etiket:
; döngü gövdesi
loop etiketEtkisi bakımından LOOP, DEC CX ve JNZ etiket çiftine benzer; ama bayrakları korur. LOOPE ve LOOPNE ise döngüyü bir koşul daha sağlandığı sürece sürdürür. LOOPE (LOOPZ), CX ≠ 0 ve ZF = 1 olduğu sürece atlar; eşit öğeler bulundukça sürer, ilk farklılıkta durur. LOOPNE (LOOPNZ), CX ≠ 0 ve ZF = 0 olduğu sürece atlar; eşit olmayan öğeler oldukça sürer, aranan değer bulununca durur. Bu iki komut, “en çok n kez, ama koşul değişirse daha önce” biçimindeki arama döngüleri için tasarlanmıştır. ZF'yi döngü gövdesindeki bir CMP ayarlar.
Bu sıranın bir tuzağı vardır. CX = 0 iken döngüye girilirse gövde bir kez çalışır, sonra LOOP CX'i azaltır. 0 − 1 = FFFFh'dir. CX sıfır olmadığı için geri atlanır. Döngü CX yeniden sıfıra inene kadar sürer ve gövde toplam 65 536 kez çalışır. Döngü sayısı çalışma anında hesaplanıyorsa ve sıfır olabiliyorsa bu durum özel olarak önlenmelidir. Önlemin adı JCXZ'dir.
Döngü (loop), bir komut grubunun bir koşul sağlanana kadar yinelenmesidir. Sayaçlı döngüde yineleme sayısı bir sayaç yazmacında tutulur; 8086'nın LOOP ailesi sayaç olarak CX'i kullanır, CX'i her turda bir azaltır ve CX sıfırlandığında döngüden çıkar.
7.6.3Kod izleme: LOOP
En basit LOOP örneği gövdesi boş bir döngüdür. Program yalnız LOOP'un CX üzerindeki etkisini gösterir.
org 100h
mov cx, 5 ; CX = tekrar sayısı (5)
dongu:
; döngü içeriği
loop dongu ; CX ≠ 0 ise dongu'ya atla
jmp dur ; döngü bitince dur'a atla
dur:
retmov cx, 5 0100h'de üç bayttır (B9 05 00). dongu etiketi 0103h'yi gösterir. Gövde boş olduğundan loop dongu da 0103h'dedir; kodu E2 FE'dir. Uzaklık FEh = −2'dir: LOOP komutu kendi başına atlar. jmp dur 0105h'dedir ve kodu EB 00'dır, çünkü dur zaten sonraki satırdır.
| Tur | Komut | CX (sonra) | Karar | IP (sonra) |
|---|---|---|---|---|
| — | mov cx, 5 | 0005h | — | 0103h |
| 1 | loop dongu | 0004h | ≠ 0, atlar | 0103h |
| 2 | loop dongu | 0003h | ≠ 0, atlar | 0103h |
| 3 | loop dongu | 0002h | ≠ 0, atlar | 0103h |
| 4 | loop dongu | 0001h | ≠ 0, atlar | 0103h |
| 5 | loop dongu | 0000h | = 0, çıkar | 0105h |
| — | jmp dur | 0000h | — | 0107h |
| — | ret | 0000h | — | 0000h |
Gövde (burada boş) beş kez çalışır ve LOOP beş kez yürütülür. İlk dört LOOP atlar, beşincisi atlamaz. Döngünün süresi Tablo 7.4'ten hesaplanabilir: 4 × 17 + 5 = 73 saat turu. Program bitince CX = 0000h'dir. Döngüden çıkışın ardından gelen jmp dur gereksizdir ama zararsızdır; uzaklığı 0 olan bir atlama yalnız 15 tur harcar ve kuyruğu boşaltır.
7.6.4Kod izleme: iç içe döngüler
LOOP ailesinin sayacı tek bir yazmaç olduğundan iç içe iki LOOP aynı CX'i paylaşır. İç döngü CX'e kendi sayısını yüklediği anda dış döngünün sayacı kaybolur. Çözüm, dış sayacı iç döngüden önce yığına itmek ve iç döngüden sonra geri almaktır. Aşağıdaki program bu yöntemi kullanır. Dış döngü 5 kez döner; her dış turda ekrana bir 1, ardından iç döngüde beş tane 2 yazılır. BX toplam adım sayısını tutar.
org 100h
mov bx, 0 ; toplam adım
mov cx, 5 ; dış döngü sayacı
k1:
add bx, 1
mov al, '1'
mov ah, 0eh
int 10h ; '1' yaz
push cx ; dış sayacı sakla
mov cx, 5 ; iç döngü sayacı
k2:
add bx, 1
mov al, '2'
mov ah, 0eh
int 10h ; '2' yaz
loop k2 ; iç döngü
pop cx ; dış sayacı geri al
loop k1 ; dış döngü
retKarakterler BIOS'un INT 10h hizmetiyle yazılır. AH = 0Eh iken BIOS, AL'deki karakteri imlecin bulunduğu yere yazar ve imleci bir ilerletir; bu hizmete daktilo biçimli yazım (teletype) denir. Hizmet BH'yi sayfa numarası olarak kullanır. BX bu programda 256'yı aşmadığından BH hep 0'dır ve yazım 0 numaralı sayfaya yapılır.
| Adım | Komut | BX | CX | SP | Yığın | Ekran |
|---|---|---|---|---|---|---|
| başlangıç | mov bx, 0 / mov cx, 5 | 0000h | 0005h | FFFEh | [FFFEh] = 0000h | |
| dış 1 | add bx, 1 … int 10h | 0001h | 0005h | FFFEh | [FFFEh] = 0000h | 1 |
| dış 1 | push cx | 0001h | 0005h | FFFCh | [FFFCh] = 0005h | 1 |
| iç 1 | mov cx, 5 … int 10h | 0002h | 0005h | FFFCh | [FFFCh] = 0005h | 12 |
| iç 1 | loop k2 | 0002h | 0004h | FFFCh | [FFFCh] = 0005h | 12 |
| iç 5 | loop k2 (çıkar) | 0006h | 0000h | FFFCh | [FFFCh] = 0005h | 122222 |
| dış 1 | pop cx | 0006h | 0005h | FFFEh | [FFFEh] = 0000h | 122222 |
| dış 1 | loop k1 | 0006h | 0004h | FFFEh | [FFFEh] = 0000h | 122222 |
| dış 2 sonu | loop k1 | 000Ch | 0003h | FFFEh | [FFFEh] = 0000h | 122222 ×2 |
| dış 3 sonu | loop k1 | 0012h | 0002h | FFFEh | [FFFEh] = 0000h | 122222 ×3 |
| dış 4 sonu | loop k1 | 0018h | 0001h | FFFEh | [FFFEh] = 0000h | 122222 ×4 |
| dış 5 sonu | loop k1 (çıkar) | 001Eh | 0000h | FFFEh | [FFFEh] = 0000h | 122222 ×5 |
| son | ret | 001Eh | 0000h | 0000h | boş | 122222 ×5 |
Program başladığında SP = FFFEh'dir ve yığının tepesinde, DOS'un koyduğu dönüş adresi 0000h durur. İlk dış turda BX = 1 olur ve ekrana 1 yazılır. AL = '1' = 31h, AH = 0Eh'dir. push cx dış sayacın değerini, 5'i, yığına iter. SP iki azalır ve FFFCh olur; [FFFCh] = 0005h'dir. Little-endian düzende FFFCh'de 05h, FFFDh'de 00h baytı durur.
İç döngü CX'i yeniden 5 yapar. Dış sayacın değeri artık yalnız yığındadır. İç döngü beş kez döner, her turda BX'i artırır ve bir 2 yazar. Beşinci iç turun LOOP'u CX'i sıfırlar ve döngüden çıkar. Bu noktada BX = 6, CX = 0'dır. pop cx yığındaki 0005h'yi CX'e geri yükler ve SP = FFFEh olur. loop k1 CX'i 4 yapar ve dış döngünün başına döner.
Her dış tur BX'e 1 + 5 = 6 ekler. Beş turdan sonra BX = 30 = 001Eh olur. Ekranda beş kez 122222 yazılıdır, yani 30 karakter. Son LOOP CX'i sıfırlar ve döngüden çıkılır. push ile pop sayısı eşit olduğu için yığın başlangıçtaki hâline dönmüştür. ret yığının tepesindeki 0000h'yi IP'ye alır, SP = 0000h olur ve program biter.
İç döngünün loop k2 komutu 011Ch'dedir ve kodu E2 F5'tir: sonraki adres 011Eh, hedef k2 = 0113h, uzaklık −0Bh = F5h. Dış döngünün loop k1 komutu 011Fh'dedir ve kodu E2 E5'tir: sonraki adres 0121h, hedef k1 = 0106h, uzaklık −1Bh = E5h. Dış döngünün gövdesi 27 bayttır ve rel8'in sınırına (128) daha çok uzaktır.
İç içe döngüde push cx / pop cx çiftini unutmak. İç döngü bitince CX = 0 olur. Dış döngünün LOOP'u bu sıfırı azaltır ve FFFFh yapar; dış döngü 5 yerine on binlerce kez döner. Çiftin yalnız birini yazmak daha da kötüdür. Yığın dengesi bozulur ve programın sonundaki ret yanlış bir değeri dönüş adresi sanır.
7.6.5Kod izleme: LOOPNE ile arama
LOOPNE, bir dizide bir değeri aramak için biçilmiş kaftandır. Döngü iki durumdan birinde biter: değer bulunduğunda (ZF = 1) ya da bütün elemanlar incelendiğinde (CX = 0). Aşağıdaki program beş elemanlı bir dizide 3 değerini arar.
org 100h
mov cx, 5 ; eleman sayısı
mov si, -1 ; ilk inc ile 0 olur
ara:
inc si ; sonraki eleman
cmp dizi[si], 3 ; aranan değer 3 mü?
loopne ara ; CX≠0 ve ZF=0 ise sürdür
jne yok ; ZF=0 → bulunamadı
mov al, 1 ; bulundu, konum SI'da
ret
yok:
mov al, 0 ; bulunamadı
ret
dizi db 7, 1, 3, 9, 4SI'nın −1 (FFFFh) ile başlatılması bir inceliktir. Döngünün ilk komutu inc si'dir ve SI'yı 0 yapar. Böylece SI her turda incelenen elemanın indisini gösterir ve döngüden çıkıldığında bulunan elemanın konumunu taşır. cmp dizi[si], 3 komutu 5 bayttır (80 BC 16 01 03): işlem kodu, ModR/M, dizinin 16 bitlik adresi (0116h) ve sabit. Dizi DB ile tanımlandığından karşılaştırma bayt boyutundadır.
| Tur | SI | dizi[SI] | CMP sonrası ZF CF SF | LOOPNE sonrası CX | Karar |
|---|---|---|---|---|---|
| 1 | 0000h | 7 | 0 0 0 | 0004h | CX ≠ 0, ZF = 0 → sürdür |
| 2 | 0001h | 1 | 0 1 1 | 0003h | CX ≠ 0, ZF = 0 → sürdür |
| 3 | 0002h | 3 | 1 0 0 | 0002h | ZF = 1 → çık |
Birinci turda inc si SI'yı FFFFh'den 0000h'ye getirir. Bu artırma sıfır sonuç verdiği için ZF = 1 yapar, ama hemen ardından gelen CMP bayrakları yeniden ayarlar. 7 − 3 = 4 sıfır değildir: ZF = 0. LOOPNE CX'i 4 yapar; CX ≠ 0 ve ZF = 0 olduğundan aramaya devam eder. İkinci turda dizi[1] = 1'dir. 1 − 3 = −2 (FEh) için ödünç alınır; CF = 1, SF = 1 olur. ZF yine 0'dır ve döngü sürer.
Üçüncü turda dizi[2] = 3'tür ve 3 − 3 = 0 ZF = 1 yapar. LOOPNE CX'i yine azaltır ve 2 yapar. CX sıfır değildir ama ZF = 1 olduğundan atlamaz; döngüden çıkılır. LOOPNE bayrakları değiştirmediği için ZF = 1 korunur. Döngüden sonraki jne yok tam bu bayrağa bakar. ZF = 1 olduğundan atlamaz ve mov al, 1 “bulundu” sonucunu yazar. SI = 2, bulunan elemanın konumudur. Döngü beş yerine üç turda bitmiştir.
Döngüden sonra neden ZF'ye bakıldığı önemlidir. Döngü iki nedenle bitebilir ve CX'e bakmak bu nedenleri ayırt etmez: değer son elemanda bulunursa CX de sıfıra iner. Son karşılaştırmanın sonucu ise ZF'de durur ve LOOPNE onu bozmaz. Dizi 3 içermeseydi beş tur sonunda CX = 0, ZF = 0 olurdu; jne yok atlar ve AL = 0 olurdu. Bu yapı, LOOP ailesinin bayrakları korumasının doğrudan bir yararıdır.
7.6.6JCXZ; LOOP mu, DEC–JNZ mi?
JCXZ etiket komutu CX = 0 ise atlar; CX'i azaltmaz ve bayraklara bakmaz. Başlıca kullanımı sıfır turlu döngüye karşı korumadır. Döngü sayısı çalışma anında hesaplanıyorsa döngünün önüne bir JCXZ konur:
mov cx, n ; n çalışma anında
jcxz atla ; CX = 0 ise girme
tekrar:
; gövde
loop tekrar
atla:JCXZ olmadan CX = 0 iken LOOP önce CX'i FFFFh yapar ve gövde 65 536 kez çalışır. JCXZ ile bu durumda döngünün üzerinden atlanır. Komut, CX'e başka bir komutla bakmadan CX'in sıfır olup olmadığını sınar; CX'i sıfırla karşılaştıran bir CMP CX, 0 bayrakları bozardı, JCXZ bozmaz.
Bir sayaçlı döngü LOOP yerine DEC ile JNZ çiftiyle de yazılabilir. İki yazımın farkları Tablo 7.21'de toplanmıştır.
| Özellik | LOOP | DEC CX + JNZ |
|---|---|---|
| Boyut | 2 bayt | 1 + 2 = 3 bayt |
| Bayraklar | değişmez | ZF, SF, OF, PF, AF değişir (CF korunur) |
| Sayaç | yalnız CX | herhangi bir yazmaç |
| Ek koşul | LOOPE / LOOPNE | farklı Jcc seçilebilir |
| 8086'da süre (atlarsa / atlamazsa) | 17 / 5 tur | 2 + 16 = 18 / 2 + 4 = 6 tur |
8086'da LOOP hem daha kısa hem biraz daha hızlıdır. On turluk bir döngüde LOOP, dokuz atlama ve bir çıkışla 9 × 17 + 5 = 158 tur harcar. DEC–JNZ çifti 10 × 2 + 9 × 16 + 4 = 168 tur harcar. Öte yandan DEC–JNZ daha esnektir: sayaç olarak CX'ten başka bir yazmaç kullanılabilir. Bu, CX'i başka bir iş için (örneğin bir kaydırma sayısı ya da dizgi komutunun sayacı olarak) kullanan döngülerde önemlidir. Sonraki x86 işlemcilerde LOOP komutu yavaşladı ve derleyiciler onu kullanmaz oldu; ama 8086 için durum tersidir.
Bayrak farkı da önemlidir. DEC, CF dışındaki durum bayraklarını sonuca göre ayarlar. Döngü boyunca CF'de saklanan bir bilgi (örneğin çok sözcüklü bir toplamanın eldesi) DEC'ten etkilenmez. Ama ZF ya da SF'de saklanan bir bilgi bozulur. LOOP hiçbir bayrağa dokunmaz. Çok sözcüklü toplama gibi eldenin turdan tura taşındığı döngülerde iki yazım da CF'yi korur; bu yüzden ikisi de kullanılabilir.
7.6.7Kod izleme: JCXZ koruması ve DEC–JNZ
Aşağıdaki program iki döngü içerir. Birinci döngü n = 0 ile çalıştırılır ve JCXZ onu korur. İkinci döngü n = 3 ile DEC–JNZ yazımıyla çalışır ve sayaç olarak BX'i kullanır. İkinci döngüden önce CF = 1 yapılır; döngü boyunca CF'nin korunduğu gözlenir.
MOV CX, 0 ; n = 0
MOV AX, 0
JCXZ atla ; CX = 0 → girme
tekrar: INC AX
LOOP tekrar
atla: MOV BX, 3 ; n = 3, sayaç BX
STC ; CF = 1
tekrar2: INC AX
DEC BX ; CF korunur
JNZ tekrar2
RET| Adım | Komut | AX | BX | CX | ZF | CF | Karar |
|---|---|---|---|---|---|---|---|
| 1–2 | MOV CX, 0 / MOV AX, 0 | 0000h | 0000h | 0000h | 0 | 0 | — |
| 3 | JCXZ atla (E3 03) | 0000h | 0000h | 0000h | 0 | 0 | CX = 0 → atlar, 010Bh |
| 4 | MOV BX, 3 | 0000h | 0003h | 0000h | 0 | 0 | — |
| 5 | STC | 0000h | 0003h | 0000h | 0 | 1 | — |
| 6–8 | INC AX / DEC BX / JNZ | 0001h | 0002h | 0000h | 0 | 1 | atlar (tur 1) |
| 9–11 | INC AX / DEC BX / JNZ | 0002h | 0001h | 0000h | 0 | 1 | atlar (tur 2) |
| 12–14 | INC AX / DEC BX / JNZ | 0003h | 0000h | 0000h | 1 | 1 | atlamaz (tur 3) |
| 15 | RET | 0003h | 0000h | 0000h | 1 | 1 | IP = 0000h |
Üçüncü adımda JCXZ, CX = 0 olduğunu görür ve döngünün üzerinden atlar. INC AX ve LOOP tekrar hiç yürütülmez; AX = 0 kalır. Koruma olmasaydı gövde 65 536 kez çalışır ve AX sonunda yeniden 0000h olurdu (65 536 = 1 0000h, 16 bite sığmaz). Sonuç aynı görünürdü, ama işlemci boşuna 65 536 tur dönerdi. Gövde AX'i değil bir bellek dizisini değiştirseydi sonuç felaket olurdu.
İkinci döngüde sayaç BX'tir; LOOP bunu yapamazdı. STC CF'yi 1 yapar. Her turda INC AX ve DEC BX bayrakları ayarlar, ama ikisi de CF'ye dokunmaz. Birinci turda BX = 2, ZF = 0 olur ve JNZ geri atlar. İkinci turda BX = 1'dir. Üçüncü turda DEC BX BX'i 0 yapar ve ZF = 1 olur. JNZ atlamaz ve döngü biter. Sonuçta AX = 3'tür ve CF = 1 korunmuştur. Üçüncü turda INC AX AX'i 3 yaptığında PF = 1 olur (0000 0011b iki tane 1 içerir); bu da DEC ile yeniden ayarlanır. LOOP da bayrak değiştirmez, ama yalnız CX ile çalışır.
7.7Örnek programlar
7.7.1Dizi elemanlarının toplamı
Bir dizinin elemanlarını toplamak, sayaçlı döngünün en tipik kullanımıdır. Aşağıdaki program beş baytlık bir diziyi toplar ve sonucu m değişkenine yazar. CX eleman sayısını, BX indisi, AL toplamı tutar.
org 100h
mov cx, 5 ; eleman sayısı
mov al, 0 ; toplam (AL) = 0
mov bx, 0 ; BX = indis
next:
add al, vector[bx] ; elemanı topla
inc bx ; sonraki eleman
loop next ; CX = 0 olana kadar dön
mov m, al ; sonucu m'ye yaz
ret
vector db 5, 4, 5, 2, 1
m db 0Veriler ret'ten sonra tanımlanmıştır. Program ret ile bittiği için işlemci veri baytlarına hiç ulaşmaz ve onları komut sanmaz. Assembler kodu yerleştirdiğinde vector 0113h'de, m 0118h'de durur. add al, vector[bx] komutu 4 bayttır (02 87 13 01): ModR/M baytı 87h, “[BX] + 16 bitlik yer değiştirme” biçimini seçer ve yer değiştirme vector'ün adresidir. loop next 010Dh'dedir; kodu E2 F9, uzaklık 0108h − 010Fh = −7'dir.
| Tur | Eklenen | AL (onluk) | AL | BX | CX | LOOP |
|---|---|---|---|---|---|---|
| başlangıç | — | 0 | 00h | 0000h | 0005h | — |
| 1 | vector[0] = 5 | 5 | 05h | 0001h | 0004h | atlar |
| 2 | vector[1] = 4 | 9 | 09h | 0002h | 0003h | atlar |
| 3 | vector[2] = 5 | 14 | 0Eh | 0003h | 0002h | atlar |
| 4 | vector[3] = 2 | 16 | 10h | 0004h | 0001h | atlar |
| 5 | vector[4] = 1 | 17 | 11h | 0005h | 0000h | çıkar |
Her turda üç komut yürütülür. add al, vector[bx] elemanı AL'ye ekler; etkin adres 0113h + BX'tir ve kesim DS'dir. inc bx indisi bir artırır, çünkü elemanlar birer bayttır. Sözcük dizisinde indis ikişer artırılırdı. loop next CX'i azaltır ve sıfır değilse başa döner. Beşinci turdan sonra CX = 0 olur ve döngüden çıkılır. mov m, al sonucu 0118h adresine yazar: m = 11h = 17 = 5 + 4 + 5 + 2 + 1.
Döngü gövdesindeki ADD bayrakları her turda yeniden ayarlar, ama LOOP onları hiç okumaz ve değiştirmez. Örneğin dördüncü turda 0Eh + 02h = 10h toplamasında düşük dörtlükten elde çıkar ve AF = 1 olur. Bu bilgiyi hiçbir komut kullanmaz. Toplamın bir bayta sığmadığı durumu ise CF bildirir. Bu dizide toplam 17'dir ve hiçbir turda CF = 1 olmaz. Elemanlar daha büyük olsaydı AL taşabilirdi; bu durum alıştırmalardan birinde incelenir.
7.7.2Ekrana Hello yazdırma
Aşağıdaki program, sonu 0 baytıyla işaretlenmiş bir dizgiyi karakter karakter ekrana yazar. Döngü sayısı önceden bilinmez; döngü 0 baytına rastlayınca biter. Bu yüzden LOOP yerine CMP, JE ve JMP kullanılır. Bu yapı bir while döngüsüdür: koşul her turun başında sınanır.
org 100h
jmp start
msg db 'Hello', 0
start:
mov si, 0 ; kaynak indisi = 0
next_char:
mov al, msg[si] ; karakteri al
cmp al, 0 ; 0 ile karşılaştır
je stop ; 0 ise yazmayı durdur
mov ah, 0eh ; teletype işlevi
int 10h ; AL'deki karakteri yaz
inc si ; sonraki karakter
jmp next_char ; başa dön
stop:
retBu programda veri kodun arasındadır. msg, programın ilk komutunun hemen arkasında, 0102h'den başlayarak 48h 65h 6Ch 6Ch 6Fh 00h baytlarını tutar. İlk komut jmp start (EB 06) bu altı baytın üzerinden 0108h'ye atlar. JMP olmasaydı işlemci 0102h'deki 48h baytını bir komut olarak çözerdi. 48h, DEC AX komutunun kodudur; arkasından gelen baytlar da başka komutlar gibi çözülür ve programın ne yapacağı öngörülemez.
| Tur | SI (başta) | AL = msg[SI] | ZF | je stop | Ekran |
|---|---|---|---|---|---|
| 1 | 0000h | 48h 'H' | 0 | atlamaz | H |
| 2 | 0001h | 65h 'e' | 0 | atlamaz | He |
| 3 | 0002h | 6Ch 'l' | 0 | atlamaz | Hel |
| 4 | 0003h | 6Ch 'l' | 0 | atlamaz | Hell |
| 5 | 0004h | 6Fh 'o' | 0 | atlamaz | Hello |
| 6 | 0005h | 00h | 1 | atlar | Hello |
Her turda mov al, msg[si] (8A 84 02 01) sıradaki karakteri AL'ye alır. cmp al, 0 (3C 00) onu sıfırla karşılaştırır. Karakter sıfır değilse ZF = 0 olur ve je stop atlamaz. AH = 0Eh ve INT 10h ile karakter ekrana yazılır. inc si indisi artırır ve jmp next_char (EB F1) başa döner. Bu JMP 0118h'dedir; uzaklık 010Bh − 011Ah = −0Fh = F1h'dir.
Altıncı turda msg[5] = 0 okunur. 0 − 0 = 0 olduğundan ZF = 1 olur ve je stop atlar. Ekranda Hello yazılıdır, SI = 5'tir. ret programı bitirir. Program sonunda AX = 0E00h'dir: AH, son turdaki 0Eh değerini korur, AL ise son okunan 00h'dir.
Sonu sıfırla işaretlenen dizgilere sıfır sonlu dizgi denir. Bu düzende uzunluk ayrıca saklanmaz; dizgi 0 baytına kadar sürer. Aynı düzeni C dili de kullanır. DOS'un INT 21h/09h hizmeti ise farklı bir işaret kullanır: dizginin sonu '$' karakteridir. İki düzenin ortak yanı, döngünün sayaçla değil bir koşulla bitmesidir.
Programdaki her tur yedi komut yürütür; bunların iki tanesi akış kontrol komutudur. Atlanmayan je 4, atlanan jmp 15 saat turu harcar. Döngü do-while biçiminde yeniden yazılsaydı (karşılaştırma ve koşullu geri atlama sonda) her turda bir JMP eksik olurdu. Kısa bir dizgide fark önemsizdir; çok uzun bir metinde ise toplam sürenin önemli bir kısmını oluşturur.
7.7.3Palindrom kontrolü: fikir
Palindrom, baştan ve sondan okunuşu aynı olan dizidir: 123321, kayak, ey edip adanada pide ye (boşluklar sayılmazsa). Bir dizinin palindrom olup olmadığını anlamak için iki işaretçi kullanılır. DI dizinin başını, SI sonunu gösterir. Her adımda iki karakter karşılaştırılır; sonra DI bir ileri, SI bir geri gider. Karakterlerden biri farklıysa dizi palindrom değildir. Bütün çiftler eşleşirse palindromdur.
Uzunluğun yarısı kadar karşılaştırma yeterlidir. Altı karakterlik bir dizide 3 çift vardır: (1., 6.), (2., 5.), (3., 4.). Daha fazla karşılaştırma aynı çiftleri ters sırayla yeniden sınar. Tek uzunlukta ortadaki karakterin bir eşi yoktur ve karşılaştırılmaz; 5 karakterde 2 çift sınanır. Yarıya bölme işlemi shr cx, 1 ile yapılır: bir bit sağa kaydırma, işaretsiz bir sayıyı 2'ye böler ve kalanı atar.
str dizisi bellekte 0102h'den başlar; DI baştan, SI sondan ilerler. Dizinin ardında satır sonu (0Dh, 0Ah) ve '$' durur.Programda dizinin uzunluğu bir sabittir: str_size = $ - msg. Burada $, assembler'ın o satırdaki konum sayacıdır, yani dizinin hemen arkasındaki adrestir. msg etiketi ile str aynı adresi gösterir; fark dizinin uzunluğu olan 6'dır. = yönergesi EQU gibi bir sabit tanımlar ve bellekte yer kaplamaz. Dizi değişirse uzunluk kendiliğinden güncellenir.
Program dizinin kendisini ve sonucu DOS'un INT 21h/09h hizmetiyle yazar. Bu hizmet DS:DX adresindeki dizgiyi '$' karakterine kadar ekrana basar; '$' yazılmaz. Dizinin ardındaki 0Dh ve 0Ah baytları satır başı ve yeni satır karakterleridir. Sonuç mesajı bu sayede bir alt satıra yazılır. Şekildeki adresler org 100h ve iki baytlık kısa jmp start (0100h) varsayımıyla verilmiştir: str 0102h'den başlar.
7.7.4Palindrom: hazırlık
org 100h
jmp start
msg:
str db '123321'
str_size = $ - msg ; = 6
db 0Dh,0Ah,'$'
start:
mov ah, 9 ; '$'e kadar yaz
mov dx, offset str
int 21h
lea di, str ; DI = str adresi
mov si, di
add si, str_size ; SI = son + 1
dec si ; SI = son karakter
mov cx, str_size
cmp cx, 1 ; tek karakter mi?
je is_palindrome ; tek karakter palindromdur
shr cx, 1 ; CX = uzunluk / 2Programın başındaki jmp start (EB 09) dokuz baytlık veriyi (altı karakter, 0Dh, 0Ah, '$') atlar ve 010Bh'ye gider. mov ah, 9 ve mov dx, offset str DOS'un dizgi yazma hizmetini hazırlar: DX = 0102h. int 21h ekrana 123321 ve satır sonunu yazar. DOS bu hizmetten AL = 24h ('$') ile döner; AX = 0924h olur.
lea di, str DI'ya str'nin adresini, 0102h'yi yükler. Burada etkin adres doğrudan bir sabittir; emu8086 bu komutu mov di, offset str ile aynı 3 baytlık koda (BF 02 01) çevirir. mov si, di SI'yı da 0102h yapar. add si, str_size 6 ekler: SI = 0108h. Bu, dizinin son karakterinin bir sonrasıdır (0Dh baytı). dec si SI'yı 0107h'ye, son karakter '1''e getirir. mov cx, str_size CX'e 6 yükler.
cmp cx, 1 ve je is_palindrome tek karakterlik dizi için bir kısa yoldur. Tek karakterli dizi her zaman palindromdur; ama asıl neden başkadır. Uzunluk 1 iken shr cx, 1 CX'i 0 yapar ve LOOP'lu döngüye CX = 0 ile girilirse karşılaştırma 65 536 kez çalışırdı. Burada CX = 6'dır: 6 − 1 ≠ 0, ZF = 0, CF = 0; JE atlamaz. shr cx, 1 CX'i 0110b'den 0011b'ye kaydırır: CX = 3. Dışarı kayan bit 0'dır, bu yüzden CF = 0 olur.
| Adres | Komut | Kod | Sonuç |
|---|---|---|---|
| 0100h | jmp start | EB 09 | IP = 010Bh |
| 010Bh | mov ah, 9 | B4 09 | AH = 09h |
| 010Dh | mov dx, offset str | BA 02 01 | DX = 0102h |
| 0110h | int 21h | CD 21 | ekran: 123321 ↵; AL = 24h |
| 0112h | lea di, str | BF 02 01 | DI = 0102h |
| 0115h | mov si, di | 8B F7 | SI = 0102h |
| 0117h | add si, str_size | 83 C6 06 | SI = 0108h |
| 011Ah | dec si | 4E | SI = 0107h |
| 011Bh | mov cx, str_size | B9 06 00 | CX = 0006h |
| 011Eh | cmp cx, 1 | 83 F9 01 | ZF = 0, CF = 0 |
| 0121h | je is_palindrome | 74 0E | atlamaz |
| 0123h | shr cx, 1 | D1 E9 | CX = 0003h, CF = 0 |
7.7.5Palindrom: karşılaştırma döngüsü
next_char:
mov al, [di] ; baştaki karakter
mov bl, [si] ; sondaki karakter
cmp al, bl
jne not_palindrome ; farklıysa değil
inc di ; baştan bir ileri
dec si ; sondan bir geri
loop next_char
is_palindrome:
mov ah, 9 ; "palindrome!" yaz
mov dx, offset msg1
int 21h
jmp stop
not_palindrome:
mov ah, 9 ; "not palindrome!" yaz
mov dx, offset msg2
int 21h
stop:
ret
msg1 db "palindrome!$"
msg2 db "not palindrome!$"Döngü next_char etiketiyle 0125h'den başlar. Her turda mov al, [di] baştaki, mov bl, [si] sondaki karakteri okur. cmp al, bl ikisini karşılaştırır. Farklılarsa jne not_palindrome döngüden hemen çıkar. Eşitlerse inc di ve dec si işaretçileri birbirine yaklaştırır; loop next_char CX'i azaltır ve başa döner.
| Tur | DI | SI | AL | BL | CMP sonrası ZF | INC/DEC sonrası DI, SI, ZF | LOOP sonrası CX |
|---|---|---|---|---|---|---|---|
| 1 | 0102h | 0107h | 31h '1' | 31h '1' | 1 | 0103h, 0106h, 0 | 0002h |
| 2 | 0103h | 0106h | 32h '2' | 32h '2' | 1 | 0104h, 0105h, 0 | 0001h |
| 3 | 0104h | 0105h | 33h '3' | 33h '3' | 1 | 0105h, 0104h, 0 | 0000h |
Her turda CMP eşitliği bulur ve ZF = 1 yapar; JNE atlamaz. Ardından gelen inc di ve dec si de bayrakları ayarlar. Sonuçları sıfır olmadığından ZF yeniden 0 olur. Bu, döngünün doğruluğunu etkilemez, çünkü karar CMP'nin hemen arkasındaki JNE'de verilmiştir. Döngü kararı ise LOOP'a aittir ve LOOP yalnız CX'e bakar. Üçüncü turdan sonra CX = 0 olur ve döngüden çıkılır: üç çiftin hepsi eşleşmiştir. Bu noktada DI = 0105h, SI = 0104h'dir; işaretçiler birbirini geçmiştir.
Döngüden çıkan işlemci sıradan düşerek is_palindrome bloğuna girer. AH = 09h ve DX = offset msg1 ile DOS'un dizgi yazma hizmeti çağrılır. msg1, kodun hemen ardından 0142h'de durur; DX = 0142h olur. Ekrana palindrome! yazılır ve AL yine 24h ile döner. jmp stop (EB 07) not_palindrome bloğunun üzerinden atlar ve ret programı bitirir.
Veriler stop: etiketinin hemen ardından gelir. ret olmasaydı işlemci palindrome!$ baytlarını komut sanıp yürütmeye başlardı. Programın uzunluğu 94 bayttır (0100h–015Dh): msg1 12, msg2 16 bayt tutar.
7.7.6Palindrom: farklı girişler
Programın farklı girişlerde nasıl davrandığını görmek, yazılan koşulların gerekli olup olmadığını anlamanın en iyi yoludur. Tablo 7.27 beş girişi karşılaştırır.
| str | Uzunluk | CX (shr sonrası) | Karşılaştırmalar | Çıktı |
|---|---|---|---|---|
'123321' | 6 | 3 | 1=1, 2=2, 3=3 | palindrome! |
'123421' | 6 | 3 | 1=1, 2=2, 3≠4 → jne | not palindrome! |
'12321' | 5 | 2 | 1=1, 2=2 (ortadaki 3 atlanır) | palindrome! |
'7' | 1 | — | cmp cx,1 → je is_palindrome | palindrome! |
'ab' | 2 | 1 | a≠b → jne | not palindrome! |
İkinci girişte üçüncü turda '3' ile '4' karşılaştırılır; ZF = 0 olur ve jne not_palindrome döngüden çıkar. LOOP'a hiç ulaşılmaz ve CX = 1'de kalır. Üçüncü girişte uzunluk 5'tir; shr 5'i 2'ye böler, kalan 1 atılır (CF = 1 olur). İki çift karşılaştırılır, ortadaki karakter tek başına kalır. Dördüncü girişte cmp cx, 1 denetimi işe yarar: uzunluk 1 iken shr cx, 1 CX'i 0 yapardı ve LOOP'lu döngü 65 536 kez çalışırdı. Uzunluk 0 da aynı tehlikeyi taşır. Boş bir dizi için CX = 0'dır, cmp cx, 1 eşit bulmaz, shr 0'ı yine 0 yapar. Bu durum döngüden önce bir jcxz is_palindrome ile önlenebilir.
7.8Sık yapılan hatalar
Akış kontrolündeki hataların çoğu birkaç kalıba indirgenebilir. Aşağıdaki liste bu kalıpları ve önlemlerini toplar.
Yanlış atlama ailesi. İşaretli veride JA/JB, işaretsiz veride JG/JL kullanmak. AL = FBh, BL = 03h için JA atlar (251 > 3), JG atlamaz (−5 < 3). Hata yalnız en anlamlı biti 1 olan değerlerde ortaya çıkar ve küçük deneme verileriyle gizli kalır. Önlem: her karşılaştırmada verinin türü belirlenir ve aile buna göre seçilir.
Unutulan JMP. if/else kalıbında A bloğunun sonundaki JMP son yazılmazsa A bloğu bittiğinde işlemci B bloğuna da girer. Bloklar etiketlerle ayrılır; etiket bir duvar değildir, yalnız bir adrestir.
CMP ile atlama arasına bayrak değiştiren komut girmesi. CMP AL, BL / INC SI / JE eşit yazımında JE, CMP'nin değil INC'in sonucuna bakar. Araya girmesi gereken bir komut varsa bayrak değiştirmeyen bir komut (MOV, LEA, PUSH, POP) seçilir ya da CMP atlamanın hemen önüne taşınır.
CX = 0 ile LOOP. LOOP önce azaltır, sonra sınar. CX = 0 iken girilen döngü 65 536 kez döner. Önlem: döngü sayısı çalışma anında belirleniyorsa döngünün önüne JCXZ konur.
İç içe döngüde CX'i saklamamak. İç döngünün MOV CX, n komutu dış sayacı siler. Önlem: iç döngüden önce PUSH CX, sonra POP CX; ya da dış döngüde DEC–JNZ ile başka bir sayaç yazmacı.
Veriyi kod gibi yürütmek. Veriler kodun önündeyse JMP start unutulur, kodun arkasındaysa son RET unutulur. İki durumda da işlemci veri baytlarını komut sanar. 'H' karakteri (48h) DEC AX olarak yürütülür; ardından gelen baytlar rastgele komutlara dönüşür.
Menzil dışı Jcc'yi eş anlamlıyla düzeltmek. JE yerine JZ yazmak ya da SHORT eklemek menzili değiştirmez. Doğru çözüm ters koşulla kısa atlama ve yakın JMP'dir.
Döngü gövdesinde sayacı değiştirmek. LOOP'lu bir döngünün gövdesinde CX'i değiştiren bir komut (örneğin DEC CX) döngü sayısını bozar. CX = 3 ile başlayan ve gövdesinde DEC CX bulunan bir döngüde CX her turda iki azalır. LOOP sonrasında CX tek değerlerden geçer: 1, FFFFh, FFFDh, … CX sıfırı hiç LOOP'tan sonra görmediği için döngü hiç bitmez.
7.9Alıştırmalar ve çözümleri
Sunumun alıştırmaları ve çözümleri aşağıdadır. Çözümler ara adımlarıyla verilmiştir.
0120h adresindeki JNE komutu 0100h adresine atlamak istiyor. Makine kodu nedir?
JNE'nin işlem kodu 75h'dir ve komut 2 bayttır. Sonraki IP = 0122h. Uzaklık = 0100h − 0122h = −22h. −22h'nin ikiye tümleyeni: 22h = 0010 0010b, tersi 1101 1101b, artı 1: 1101 1110b = DEh. Kod
75 DE'dir. Sağlama: 0122h + FFDEh = 1 0100h → IP = 0100h.mov al, 80h/cmp al, 1sonrasında JA ve JG atlar mı?80h − 01h = 7Fh. Ödünç yoktur (CF = 0), sonuç sıfır değildir (ZF = 0): JA atlar; işaretsiz yorumda 128 > 1'dir. İşaretli yorumda −128 − 1 = −129 sekiz bite sığmaz: OF = 1. Sonucun işaret biti 0'dır: SF = 0. SF ≠ OF olduğundan JG atlamaz; −128 < 1'dir. Düşük dörtlükte 0 − 1 ödünç gerektirdiği için AF = 1, 7Fh yedi tane 1 içerdiği için PF = 0 olur.
mov cx, 0/d: loop dkaç kez döner? Nasıl önlenir?LOOP önce CX'i azaltır: 0 − 1 = FFFFh ≠ 0, geri atlar. CX FFFFh'den 1'e kadar her değerde atlar, 0'da çıkar. LOOP toplam 65 536 kez yürütülür. Önlemek için döngüden önce
jcxzile CX = 0 durumu atlanır.AX'teki işaretsiz sayı 100'den büyükse BX = 1, değilse BX = 0 yapan kod.
mov bx, 0 cmp ax, 100 jbe son ; AX <= 100 ise atla (işaretsiz) mov bx, 1 son:Koşulun tersi (≤) işaretsiz aileden seçilir: JBE. Denendiğinde AX = 0, 99 ve 100 için BX = 0; AX = 101 ve FFFFh (65 535) için BX = 1 bulunur. JLE kullanılsaydı FFFFh işaretli −1 sayılır ve BX = 0 kalırdı.
Dizi toplamı örneğinde elemanlar 200 ve 100 olsaydı AL ve CF ne olurdu? Çözüm önerin.
200 + 100 = 300 = 12Ch. AL'ye yalnız düşük bayt sığar: AL = 2Ch = 44. Dokuzuncu bit CF'ye düşer: CF = 1 (elde). Sonuç yanlıştır. Çözüm toplamı 16 bitte tutmaktır. Döngüde
add al, vector[bx]komutunun arkasınaadc ah, 0eklenir ve döngüden önce AX sıfırlanır. ADC, AH'ye 0 ile birlikte CF'yi de ekler; her elde AH'ye taşınır. Bu çözümle AX = 012Ch = 300 bulunur. LOOP bayrak değiştirmediği için eldeyi bozmaz; ama ADD her turda CF'yi yeniden ayarladığı için ADC hemen ADD'in arkasında durmalıdır.Hello örneğinde
jmp startsatırı silinirse ne olur?Program 0100h'den başlar ve orada msg'nin ilk baytı, 48h ('H') durur. İşlemci bu baytı
DEC AXkomutu olarak yürütür. Arkasından gelen 65h, 6Ch, 6Ch, 6Fh, 00h baytları da komut olarak çözülür ve yürütülür. Programın davranışı öngörülemez olur; ekrana Hello yazılmaz. Assembler bu hatayı yakalamaz, çünkü veri ile kod arasındaki ayrım yalnız programcının kafasındadır.
7.10Özet
- IP ve kuyruk: işlemci komutları CS:IP'den sırayla yürütür; IP her komuttan sonra komutun uzunluğu kadar artar. BIU, sıradaki baytları altı baytlık önden getirme kuyruğuna önceden alır. Atlama kuyruğu boşaltır; bu yüzden atlanan bir atlama 15–19 saat turu sürer.
- JMP koşulsuzdur ve bayrakları değiştirmez. Kısa (EB rel8, ±128), yakın (E9 rel16, kesim içi), uzak (EA ofs seg, CS:IP) ve dolaylı (FF /4, yazmaç ya da bellek) biçimleri vardır.
- Uzaklık = hedef − (komut adresi + komut uzunluğu). Negatif uzaklık ikiye tümleyenle saklanır; işlemci onu işaret genişletip IP'ye ekler.
- Etiket bellekte yer kaplamaz; assembler onu bir bağıl konuma çevirir. Rakamla başlayamaz, boşluk içeremez, komut ya da yazmaç adı olamaz.
- Jcc bayraklara bakar, bayrakları değiştirmez. 8086'da hepsi 2 bayttır (70h–7Fh + rel8); işlem kodunun son biti koşulu tersler. Eş anlamlılar aynı koda derlenir.
- CMP sonucu saklamayan bir SUB'dır; ardından gelen Jcc kararı verir.
- İşaretsiz karşılaştırma: JA, JAE, JB, JBE → CF ve ZF. JB ≡ JC.
- İşaretli karşılaştırma: JG, JGE, JL, JLE → ZF, SF ve OF. A < B ⇔ SF ≠ OF; taşmada işaret biti ters çıktığı için SF tek başına yetmez.
- if/else: koşulun tersiyle A bloğu atlanır, A bloğunun sonunda JMP ile B bloğu atlanır. while: koşul başta sınanır, gövde sonunda JMP ile başa dönülür.
- Menzil dışı Jcc: ters koşulla kısa atlama + yakın JMP. emu8086 bu dönüşümü kendisi yapar.
- LOOP ailesi: CX azalır, bayraklar korunur; LOOPE/LOOPNE ZF'ye de bakar; JCXZ CX = 0 ise atlar. Hepsi 2 bayt ve rel8'dir.
- Tuzaklar: CX = 0 ile LOOP (65 536 tur), iç içe döngüde CX'i saklamamak, unutulan JMP, CMP ile Jcc arasına bayrak değiştiren komut, veriyi kod gibi yürütmek.
7.11Kavram listesi
Terimlerin İngilizce karşılıkları ve öteki bölümlerdeki kullanımları terim sözlüğünde bulunur.
- akış kontrolü flow control
- komut işaretçisi (IP) instruction pointer
- önden getirme kuyruğu prefetch queue
- yol arayüz birimi (BIU) bus interface unit
- yürütme birimi (EU) execution unit
- atlama jump
- koşulsuz / koşullu atlama unconditional / conditional jump
- kısa / yakın / uzak atlama short / near / far jump
- uzaklık jump displacement
- göreceli adresleme relative addressing
- etiket label
- bağıl konum offset
- işlem kodu opcode
- ikiye tümleyen two's complement
- işaret genişletme sign extension
- bayrak flag
- sıfır bayrağı (ZF) zero flag
- elde bayrağı (CF) carry flag
- işaret bayrağı (SF) sign flag
- taşma bayrağı (OF) overflow flag
- eşlik bayrağı (PF) parity flag
- ödünç borrow
- karşılaştırma (CMP) comparison
- işaretli / işaretsiz sayı signed / unsigned number
- büyük / küçük, işaretsiz above / below
- büyük / küçük, işaretli greater / less
- döngü loop
- iç içe döngü nested loop
- sayaç yazmacı counter register
- dallanma branch
- yığın stack
- dizgi string
- saat turu clock cycle
- PSP (program kesimi öneki) program segment prefix
7.12Kendini Dene
0150h adresindeki kısa JMP komutu 0140h'ye atlıyor. Makine kodu nedir? Komut 0300h'de olsaydı hangi biçim kullanılırdı?
Cevap
Sonraki IP = 0152h; uzaklık 0140h − 0152h = −12h → EEh. Kod
EB EE. 0300h'den uzaklık −1C2h (−450) olurdu ve kısa menzile sığmazdı. Yakın biçim seçilir: sonraki IP 0303h, uzaklık FE3Dh, kodE9 3D FE.AX = 8000h ve BX = 0001h iken
CMP AX, BXhangi bayrakları ayarlar? JB ve JL atlar mı?Cevap
8000h − 0001h = 7FFFh. CF = 0 (ödünç yok), ZF = 0, SF = 0 (sonucun en anlamlı biti 0), OF = 1 (işaretli −32 768 − 1 sığmaz). JB atlamaz: işaretsiz yorumda 32 768 > 1. JL atlar: SF ≠ OF, işaretli yorumda −32 768 < 1.
JL'nin işlem kodu 7Ch'dir. Tersi olan komutun işlem kodu nedir? 0108h'deki bir JL'yi 0100h'ye geri atlatmak için gereken kod nedir?
Cevap
Son bit değişir: 7Dh, yani JGE (JNL). Geri atlama: sonraki IP = 010Ah, uzaklık 0100h − 010Ah = −0Ah = F6h; kod
7C F6.Aşağıdaki döngünün gövdesinde yanlışlıkla
dec cxbırakılmıştır. CX = 4 ile başlarsa program bittiğinde AX nedir? CX = 3 ile başlarsa ne olur?mov cx, 4 mov ax, 0 d: inc ax dec cx loop dCevap
CX = 4: tur 1'de DEC 3, LOOP 2 (atlar); tur 2'de DEC 1, LOOP 0 (çıkar). AX = 2. CX = 3: tur 1'de DEC 2, LOOP 1 (atlar); tur 2'de DEC 0, LOOP FFFFh (atlar); sonra CX hep tek değerlerden geçer ve LOOP'tan sonra hiç 0 olmaz. Döngü sonsuzdur.
AL'deki işaretli sayı negatifse BL = 1, değilse BL = 0 yapan kod CMP ve tek bir koşullu atlamayla nasıl yazılır? Hangi atlama seçilmelidir?
Cevap
mov bl, 0 cmp al, 0 jge son ; AL >= 0 ise atla mov bl, 1 son:Koşulun tersi (AL ≥ 0) işaretli aileden seçilir: JGE. AL = 80h ve FFh için BL = 1; 00h, 05h ve 7Fh için BL = 0 olur. Sıfırla karşılaştırmada taşma olamayacağı için
JNSda doğru çalışır.On turluk bir döngü LOOP ile ve DEC CX + JNZ ile yazılıyor. 8086'da yalnız döngü denetimi kaç saat turu sürer?
Cevap
LOOP: 9 atlama × 17 + 1 çıkış × 5 = 158 tur. DEC CX + JNZ: 10 × 2 (DEC) + 9 × 16 + 1 × 4 = 168 tur. 8086'da LOOP hem 1 bayt kısa hem 10 tur hızlıdır.
0102h'deki
je uzakkomutunun hedefi 01CFh'dedir. emu8086 bu komutu nasıl kodlar? Toplam kaç bayt olur?Cevap
Uzaklık 01CFh − 0104h = CBh = 203 > 127; kısa biçim yetmez. Koşul terslenir:
75 03(JNE; koşul sağlanırsa JMP'nin üç baytının üzerinden geçer) veE9 C8 00(yakın JMP; sonraki IP 0107h, 01CFh − 0107h = 00C8h). Toplam 5 bayt.LOOPNE ile arama örneğinde dizi
7, 1, 8, 9, 3olsaydı döngü kaç turda biter? Döngüden sonra CX, ZF ve AL nedir?Cevap
3 son elemandadır. Beşinci turda CMP ZF = 1 yapar; LOOPNE CX'i 1'den 0'a indirir ve iki nedenle de çıkar. CX = 0, ZF = 1 olur.
jne yokZF'ye baktığı için atlamaz ve AL = 1, SI = 4 bulunur. CX'e bakılsaydı “bulunamadı” sanılırdı.
7.13İleri okuma
- B. B. Brey, The Intel Microprocessors (8. baskı), Bölüm 6 (Program Control Instructions): JMP biçimleri, koşullu atlamalar, LOOP ailesi ve yüksek düzey yapıların assembly karşılıkları; bu notun en yakın kaynağıdır.
- Intel, The 8086 Family User's Manual (1979): komut kodlama tabloları ve saat turu sayıları birinci elden buradadır; önden getirme kuyruğunun işleyişi de anlatılır.
- K. R. Irvine, Assembly Language for x86 Processors (7. baskı), Bölüm 6 (Conditional Processing): CMP, işaretli ve işaretsiz atlamalar, bileşik koşullar ve döngü örnekleri.
- emu8086 belgeleri, 8086 assembler tutorial for beginners, Part 7 (Program Flow Control): sunumdaki örneklerin kaynağı; menzil dışı koşullu atlama hilesi de burada anlatılır.
7.14Kaynaklar
- emu8086 belgeleri: 8086 Assembler Tutorial for Beginners (Part 7: Program Flow Control).
- Intel, The 8086 Family User's Manual, 1979: komut kümesi ve kodlama tabloları.
- Barry B. Brey, The Intel Microprocessors, 8. baskı, Pearson, 2009: Bölüm 6 “Program Control Instructions”.
- Kip R. Irvine, Assembly Language for x86 Processors, 7. baskı, Pearson, 2014: koşullu işlem ve döngüler.
İlgili sayfalar: Bölüm 7 sunumu · Çalışma soruları · Terim sözlüğü · © Sercan Külcü. Tüm hakları saklıdır.