Mikroişlemciler · Ders Notu
Bölüm 4.2: Değişkenler
Bir programın verisi, sonuçta bellekteki baytlardan ibarettir. 8086 bu baytlara 20 bitlik fiziksel adreslerle ulaşır; ama programcı için 5A73:235B gibi bir adresle çalışmak hem zor hem de hataya açıktır. Assembly dili bu yüzden bellek konumlarına ad vermeye izin verir: değişken. Bu not önce değişkenlerin DB, DW ve DD direktifleriyle nasıl tanımlandığını anlatır. Ardından assembler'ın onlara bağıl konum atarken ne yaptığını ve çok baytlı değerlerin belleğe hangi sırayla yazıldığını gösterir. Sonra aynı fikri dizilere, dizgilere ve tablolara genişletir; bir değişkenin değeri ile adresi arasındaki farkı LEA ve OFFSET üzerinden işler; bellekte hiç yer kaplamayan EQU sabitlerini ve konum sayacını tanıtır. Son kesim, değişkenlerle veri taşıyan, toplayan, karşılaştıran ve 32 bitlik değer işleyen küçük programları satır satır izler. Her adımda komutun yazmaçları, bayrakları ve belleği nasıl değiştirdiği ve 8086'nın yollarında neler olduğu birlikte verilir.
Öğrenme çıktıları
- Değişken tanımlamak
- DB, DW, DD,
?ve DUP ile bayt, sözcük, çift sözcük değişkenleri ve dizileri tanımlamak; adlandırma kurallarını ve değer aralıklarını uygulamak. - Bellek yerleşimini çıkarmak
- Bir kaynak programın her komutunun ve her değişkeninin hangi bağıl konuma, hangi bayt sırasıyla yerleştiğini bulmak; ORG 100h'nin, PSP'nin ve hizalamanın etkisini açıklamak.
- Dizilerle çalışmak
- Bayt ve sözcük dizilerinde, dizgilerde ve iki boyutlu tablolarda bir elemanın bayt uzaklığını hesaplamak ve elemana indisle erişmek.
- Adres ile değeri ayırmak
- LEA, OFFSET ve değişken adının yazmaca neyi yüklediğini söylemek; işaretçi değişkenleri kullanmak.
- Sabitleri kullanmak
- EQU sabitlerini ve konum sayacını (
$) kullanarak bellekte yer kaplamayan, kendiliğinden güncellenen değerler yazmak. - Programları izlemek
- Değişkenler arasında veri taşıyan, toplayan, karşılaştıran ve 32 bitlik değer işleyen programları yazmaç, bayrak ve bellek düzeyinde adım adım izlemek.
4.2.1Değişkenler
8086'nın belleği, 00000h ile FFFFFh arasında numaralanmış bir milyon baytlık uzun bir şerittir. İşlemci bu şeritteki bir bayta ulaşmak için bir kesim yazmacı ve bir bağıl konumdan 20 bitlik fiziksel adres üretir. Makine dili yalnız bu sayıları tanır. Bir sayacı, bir toplamı ya da bir metni bellekte saklayan program her kullanımda verinin adresini yazmak zorunda kalabilir. Böyle bir programı okumak da değiştirmek de çok güçtür. Bir veriyi bir bayt öteye kaydırmak, o adresin geçtiği bütün komutları tek tek düzeltmek anlamına gelirdi.
Assembly dili bu sorunu adlarla çözer. Programcı bir bellek konumuna anlamlı bir ad verir; adresi hesaplama ve her kullanımda yerine koyma işini assembler üstlenir. Bu kesim, adlandırılmış bellek konumlarının, yani değişkenlerin nasıl tanımlandığını ve assembler ile işlemcinin onları nasıl gördüğünü anlatır.
4.2.1.1Değişken nedir?
Değişken (variable), bellekte bir ada bağlanmış ve değeri program çalışırken değişebilen veri alanıdır. Değişkenin üç özelliği vardır: adı, bellekteki adresi (kesim içindeki bağıl konumu) ve kapladığı bayt sayısını belirleyen türü.
Programcı için var1 adını kullanmak, 5A73:235B adresini kullanmaktan çok daha kolaydır. Ad yalnız kaynak kodda vardır. Assembler kaynak dosyayı makine koduna çevirirken her adı, o değişkene ayırdığı bağıl konumla değiştirir. Çalışan programda ad kalmaz; işlemci yalnız komutun içine gömülmüş 16 bitlik bağıl konumu görür. Bu yüzden değişken kavramı donanımda değil, assembler'da yaşar.
Assembler iki temel tür destekler. BYTE türü 8 bittir ve DB (define byte) ile tanımlanır. WORD türü 16 bittir, 8086'nın bir sözcüğüdür ve DW (define word) ile tanımlanır. Üçüncü bir direktif olan DD (define double word) 32 bitlik, yani 4 baytlık çift sözcük ayırır. Tanımın genel biçimi şöyledir:
ad DB değer
ad DW değer
ad DD değerAşağıdaki iki satır, birer baytlık ve iki baytlık iki değişken tanımlar:
counter DB 10h ; BYTE
sum DW 0FFFFh ; WORDcounter tek bayt kaplar ve 10h'yi tutar. sum iki bayt kaplar: sum ve sum+1 adreslerinde FFh ve FFh.Şekil 4.2.1 bu iki tanımın bellekteki karşılığını gösterir. Assembler counter için bir bayt ayırır ve içine 10h yazar. Hemen arkasından sum için iki bayt ayırır. sum adı bu iki baytın ilkinin adresini temsil eder. İkinci bayta sum+1 ile ulaşılır. Değişkenler arasında boşluk bırakılmaz: tanımlar kaynakta hangi sırayla yazıldıysa bellekte de o sırayla, ardışık olarak yerleşir.
Direktif (directive), assembler'a bilgi veren ve kendisi işlemci komutuna çevrilmeyen kaynak satırıdır. ORG, END, EQU hiçbir bayt üretmez. DB, DW ve DD ise komut değil veri baytı üretir: verilen ilk değerleri belleğe, programın içine yazar.
Bu ayrım önemlidir. MOV AL, counter gibi bir satır, işlemcinin yürüteceği bir makine komutudur. counter DB 10h ise yürütülecek bir şey değildir; yalnız programın bayt görüntüsüne 10h değerini koyar. Program diskten belleğe yüklendiğinde bu bayt da yüklenir ve değişkenin ilk değeri olur. Değişkene ayrıca bir başlangıç ataması yapmaya gerek kalmaz.
4.2.1.2Adlandırma kuralları ve ilk değer
Değişken adı bir etikettir ve assembler'ın sembol tablosuna yazılır. Bu yüzden bazı kurallara uymak zorundadır:
- Ad harfle başlar; ardından harf ve rakam gelebilir. MASM ve emu8086 alt çizgiyi ve birkaç özel karakteri de (
_,@,$,?) kabul eder. - Yazmaç adları (
AX,SI…) ve komut adları (MOV,ADD…) ayrılmış sözcüklerdir; değişken adı olamaz. - Assembler büyük ve küçük harfi ayırmaz:
VAR1,var1veVar1aynı addır. - Aynı ad iki kez tanımlanamaz. Bu kural sabitler ve etiketler için de geçerlidir.
Bir veri tanımının adı olmak zorunda değildir. DB 0Ah satırı bellekte bir bayt ayırır ve içine 0Ah yazar; bu baytın adı yoktur ama adresi vardır. Bir önceki adlandırılmış değişkenin adresine bir sayı eklenerek ona ulaşılabilir. Çok satırlı tablolarda yalnız ilk satıra ad verilmesi bu yüzden yeterlidir.
İlk değer herhangi bir sayı biçiminde yazılabilir: onluk (10), onaltılık (0Ah), ikili (00001010b), sekizli (12o) ya da tırnak içinde karakter ('A'). Assembler hepsini aynı ikili desene çevirir. İlk değeri önemsiz olan değişkenler için ? yazılır. ? “bu alanın başlangıç değeri tanımsızdır” demektir; emu8086 bu baytlara 00 koyar, ama programın bu değere güvenmesi yanlıştır.
| Tanım | Sonuç | Açıklama |
|---|---|---|
sayi1 DB 5 | geçerli | harfle başlayan ad, bir bayt, değer 05h |
1sayi DB 5 | geçersiz | ad rakamla başlıyor |
ax DW 0 | geçersiz | yazmaç adı değişken adı olamaz |
DB 0Ah | geçerli | adsız; bir bayt ayırır, adresi vardır |
sonuc DW ? | geçerli | ilk değersiz sözcük; emu8086'da 00 00 |
harf DB 'A' | geçerli | karakterin ASCII kodu: 41h |
4.2.1.3Değer aralıkları
Bir baytta 8 bit, yani 28 = 256 farklı desen vardır. Bu desenler iki biçimde yorumlanabilir. İşaretsiz yorumda desenler 0 ile 255 arasındaki sayılardır. İşaretli yorumda ikiye tümleyen gösterimi kullanılır: en üst bit 1 ise sayı negatiftir ve aralık −128 ile 127 arasındadır. Sözcük ve çift sözcük için aynı mantık 16 ve 32 bite uygulanır.
| Direktif | Boyut | İşaretsiz | İşaretli | Örnek |
|---|---|---|---|---|
DB | 1 bayt | 0 … 255 | −128 … 127 | x DB -1 → FF |
DW | 2 bayt | 0 … 65 535 | −32 768 … 32 767 | y DW -2 → FE FF |
DD | 4 bayt | 0 … 4 294 967 295 | −231 … 231 − 1 | z DD 1 → 01 00 00 00 |
Assembler bir ilk değeri kabul ederken iki aralığın birleşimine bakar. DB için −128 ile 255 arasındaki her sayı bir bayta sığar. x DB -1 yazıldığında assembler −1'in 8 bitlik ikiye tümleyenini hesaplar: 1'in tersi 1111 1110, artı 1, 1111 1111 = FFh. x DB 255 yazılsaydı bellekte yine FFh dururdu. İki tanım bellekte aynı baytı üretir. DB 300 ise bir bayta sığmaz, çünkü 300 = 12Ch dokuz bit ister; assembler bu satırda hata verir ve değeri sessizce kesmez.
Bellekte yalnız bit deseni saklanır; bir baytın işaretli mi işaretsiz mi olduğu bilgisi bellekte yoktur. Desenin anlamını, onu kullanan komut belirler: JAE, JB ve CF işaretsiz; JGE, JL, CBW ve OF işaretli yorum yapar.
Bu tanım, değişkenlerle çalışırken sık karşılaşılan bir karışıklığı önler. DB -1 yazmak, assembler'a “bu değişken işaretlidir” demek değildir. Programcı yalnız ilk deseni seçer. Aynı FFh baytı bir komutta 255, bir sonrakinde −1 olarak işlenebilir. Aşağıdaki izleme bunu adım adım gösterir.
4.2.1.4Kod izleme: FFh 255 mi, −1 mi?
ORG 100h
MOV AL, x ; FFh
ADD AL, 1 ; 255 + 1 ?
MOV AL, x
CBW ; işaretli: −1
MOV BX, y ; FFFEh
NEG BX ; −(−2)
RET
x DB -1
y DW -2Program 0100h'den başlar. Yedi komut toplam 16 bayt tutar (A0 10 01, 04 01, A0 10 01, 98, 8B 1E 11 01, F7 DB, C3); bu yüzden x 0110h'ye, y 0111h'ye yerleşir. Bellekte x = FFh, y ise little-endian sırayla FE FF baytlarıdır, yani FFFEh.
MOV AL, x satırı, assembler'ın gözünde MOV AL, [0110h]'dir. Makine kodu A0 10 01: A0 işlem kodu “AL'ye, ardından gelen 16 bitlik bağıl konumdaki baytı yükle” anlamına gelir; 10 01 baytları da 0110h'dir. Yürütme biriminin (EU) belleğe kendisi erişmediğini hatırlamak yararlıdır. EU bağıl konumu kuyruktan alır ve okuma işini yol arayüz birimine (BIU) verir. BIU fiziksel adresi DS × 10h + 0110h olarak hesaplar ve bir okuma yol döngüsü yapar. emu8086'nın yükleme kesimi 0700h ise fiziksel adres 07000h + 0110h = 07110h'dir. Bu adres çift olduğu için bayt veri yolunun düşük yarısından (D0–D7) gelir. Komut 10 saat turu sürer. Sonuçta AL = FFh, AX = 00FFh olur. MOV bayrakları değiştirmez.
ADD AL, 1 deseni işaretsiz bakışla işler. 1111 1111 + 0000 0001 = 1 0000 0000; dokuzuncu bit bir bayta sığmaz. AL = 00h kalır ve dışarı taşan bit elde bayrağına yazılır: CF = 1. Sonuç sıfır olduğu için ZF = 1'dir. Aynı toplama işaretli bakışla −1 + 1 = 0'dır ve doğrudur; işaretli taşma olmadığı için OF = 0 kalır. Tek bir toplama iki yorumun sonucunu aynı anda bayraklara yazar: CF işaretsiz taşmayı, OF işaretli taşmayı bildirir. Hangisine bakılacağına program karar verir.
MOV AL, x AL'yi yeniden FFh yapar; bayraklar önceki değerlerini korur. CBW (convert byte to word) AL'yi işaretli sayı kabul eder ve 16 bite genişletir: AL'nin en üst bitini AH'nin sekiz bitine kopyalar. AL = FFh'nin en üst biti 1'dir; AH = FFh olur. AX = FFFFh, 16 bitlik −1'dir. Bu işleme işaret genişletme denir. AL işaretsiz 255 olarak düşünülseydi doğru 16 bitlik karşılık 00FFh olurdu; CBW bu seçimi yapmaz, her zaman işaretli genişletir. CBW yalnız 2 saat turu sürer ve bayrakları değiştirmez.
MOV BX, y iki baytı birlikte okur: 0111h'deki FEh düşük bayta (BL), 0112h'deki FFh yüksek bayta (BH) gider. BX = FFFEh, işaretsiz bakışla 65 534, işaretli bakışla −2'dir. y tek adreste durduğu için 8086 bu sözcüğü iki ayrı yol döngüsüyle okur. NEG BX sayının ikiye tümleyenini alır, yani 0 − BX hesaplar: 0000h − FFFEh = 0002h. İşlenen sıfırdan farklı olduğu için NEG CF = 1 yapar. Sonuç sıfır değildir, ZF = 0 olur.
| Satır | AX | BX | CF | ZF | OF | Yorum |
|---|---|---|---|---|---|---|
2 MOV AL, x | 00FFh | 0000h | 0 | 0 | 0 | AL: 255 ya da −1 |
3 ADD AL, 1 | 0000h | 0000h | 1 | 1 | 0 | işaretsiz taşma: CF |
4 MOV AL, x | 00FFh | 0000h | 1 | 1 | 0 | bayraklar korunur |
5 CBW | FFFFh | 0000h | 1 | 1 | 0 | işaret genişletme: −1 |
6 MOV BX, y | FFFFh | FFFEh | 1 | 1 | 0 | 65 534 ya da −2 |
7 NEG BX | FFFFh | 0002h | 1 | 0 | 0 | 0 − (−2) = 2 |
ADD AL, 1 sonrası: AL = 00h, işaretsiz taşma CF = 1 ile bildirilir. Sağda CBW sonrası: AL = FFh işaretli −1 kabul edilerek AX = FFFFh'ye genişletilir.Tablo 4.2.3 ve Şekil 4.2.2 aynı programın iki yüzünü gösterir. Bellekteki iki değişken hiç değişmedi; değişen, desenleri okuyan komutların yorumudur. ADD ve CF deseni işaretsiz, CBW ve NEG işaretli sayı olarak ele aldı.
x DB -1 ile tanımlanan bir değişkenin bundan sonra hep negatif sayı gibi davranacağını sanmak yanlıştır. Assembler -1'i yalnız FFh desenine çevirir ve türü BYTE olarak kaydeder; işaret bilgisini saklamaz. Bu değişken JAE ile karşılaştırılırsa 255 gibi davranır.
4.2.1.5Değişkenin türü ve boyut denetimi
Assembler her değişken adının yanına bir tür de yazar. DB ile tanımlanan ad BYTE (1 bayt), DW ile tanımlanan WORD (2 bayt), DD ile tanımlanan DWORD (4 bayt) türündedir. Tür, bir komutta değişkenin kaç baytının okunacağını ya da yazılacağını belirler ve derleme anında bir güvenlik denetimi sağlar.
Kural basittir: komuttaki diğer işlenen bir yazmaçsa, yazmacın boyutu ile değişkenin türü eşleşmelidir. MOV AX, w geçerlidir, çünkü AX de w de 16 bittir. MOV AL, w ise geçersizdir; assembler “1 bayt / 2 bayt” uyuşmazlığını bildirir ve derlemeyi durdurur. Diğer işlenen bir sabitse, sabitin kaç bit yazılacağını değişkenin türü belirler: MOV w, 5 belleğe 05 00 diye iki bayt yazar.
Değişken adı içermeyen bellek işlenenlerinin türü yoktur. [BX] yalnız bir adres söyler; o adreste bir bayt mı yoksa bir sözcük mü olduğunu söylemez. Bu yüzden MOV [BX], 5 belirsizdir ve hata verir. Programcı boyutu BYTE PTR ya da WORD PTR ile açıkça yazar: MOV BYTE PTR [BX], 5. Aynı belirteç, bir değişkenin türünü yalnız o komut için değiştirmeye de yarar: MOV AL, BYTE PTR w sözcük değişkenin düşük baytını okur. Değişkenin tanımı ve türü değişmez.
| Komut | Sonuç |
|---|---|
MOV AL, w | hata: işlenen boyutları uyuşmuyor (1 bayt / 2 bayt) |
MOV AL, BYTE PTR w | AL = 34h (düşük bayt) |
MOV AL, BYTE PTR w+1 | AL = 12h (yüksek bayt) |
MOV w, 5 | w = 0005h; belleğe 05 00 yazılır |
MOV AX, b | hata: işlenen boyutları uyuşmuyor (2 bayt / 1 bayt) |
MOV [BX], 5 | hata: boyut belirsiz; BYTE PTR ya da WORD PTR gerekir |
MOV b, w | hata: bellekten belleğe MOV yoktur |
INC b | b = 06h; yalnız bir bayt artırılır |
Tür denetimi yalnız derleme anında yapılır. İşlemci çalışırken tür bilmez. Komutun boyutu makine kodunun içinde bir bitle kodlanır: 8086 işlem kodlarının çoğunda en düşük bit (W biti) 0 ise işlem bayt, 1 ise sözcük üzerindedir. Örneğin MOV AL, [adres] A0, MOV AX, [adres] A1 ile kodlanır. Assembler değişkenin türüne bakarak doğru W bitini seçer. Yanlış boyutlu bir erişim derlemeden geçebilseydi işlemci onu itirazsız yürütürdü. Bu yüzden assembler'ın verdiği boyut hatası programcının yararınadır: yanlış değer üreten ve fark edilmesi zor bir hatayı daha baştan yakalar.
MOV b, w satırındaki hata ise tür hatası değil, bir donanım kuralıdır. 8086'nın hiçbir veri taşıma komutu iki bellek işleneni almaz. Komut biçiminde yalnız bir ModR/M alanı vardır ve bu alan en çok bir bellek işleneni tanımlayabilir. Bellekten belleğe taşıma her zaman bir yazmaç üzerinden iki komutla yapılır.
4.2.1.6LABEL ile aynı belleğe iki ad
Bazen aynı bellek alanı hem bayt bayt hem de sözcük olarak işlenmek istenir. Her erişimde BYTE PTR ya da WORD PTR yazmak mümkündür ama okunaklı değildir. LABEL direktifi bunun yerine aynı adrese ikinci bir ad ve tür verir.
ad LABEL tür direktifi bellekte yer ayırmaz. Kendisinden sonra gelen ilk baytın adresine, belirtilen türde (BYTE, WORD, DWORD) yeni bir ad verir.
ORG 100h
MOV AX, sozcuk ; WORD
MOV BL, baytlar ; düşük
MOV BH, baytlar+1 ; yüksek
MOV baytlar, 0FFh ; tek bayt
MOV CX, sozcuk ; sözcük
RET
sozcuk LABEL WORD
baytlar DB 34h, 12hAltı komut 0100h ile 0114h arasını kaplar; RET 0114h'dedir. sozcuk LABEL WORD hiç bayt üretmediği için hem sozcuk hem de baytlar 0115h adresini gösterir. Bellekte 0115h'de 34h, 0116h'da 12h durur.
MOV AX, sozcuk WORD türünde bir ad kullandığı için iki baytı birden okur: AX = 1234h. Assembler bu satırı A1 15 01 olarak kodlar. MOV BL, baytlar aynı adresin BYTE türündeki adını kullanır ve yalnız 34h'yi okur; PTR yazmaya gerek kalmaz. MOV BH, baytlar+1 ikinci baytı okur: BH = 12h. BX = 1234h olur. AX ile aynı sonuç, little-endian sıranın doğal sonucudur: düşük adresteki bayt düşük yarıya gider.
MOV baytlar, 0FFh satırında hedefin türü BYTE'tır. Assembler 5 baytlık C6 06 15 01 FF komutunu üretir ve işlemci yalnız 0115h adresine yazar. Bellek artık FF 12'dir. Son okuma MOV CX, sozcuk aynı iki baytı sözcük olarak alır: CX = 12FFh. Bayt adıyla yapılan değişiklik sözcük adıyla görülür, çünkü iki ad tek bir bellek konumunu gösterir.
| Satır | AX | BX | CX | [0115h] [0116h] |
|---|---|---|---|---|
2 MOV AX, sozcuk | 1234h | 0000h | 0000h | 34 12 |
3 MOV BL, baytlar | 1234h | 0034h | 0000h | 34 12 |
4 MOV BH, baytlar+1 | 1234h | 1234h | 0000h | 34 12 |
5 MOV baytlar, 0FFh | 1234h | 1234h | 0000h | FF 12 |
6 MOV CX, sozcuk | 1234h | 1234h | 12FFh | FF 12 |
MOV AX, sozcuk: iki bayt WORD olarak okunur. Sağda MOV baytlar, 0FFh: BYTE türü nedeniyle yalnız 0115h değişir; sozcuk artık 12FFh'dir.LABEL bir tamponu hem bayt hem sözcük olarak işleyen programlarda kullanılır. Örneğin bir seri port tamponu bayt bayt doldurulur ama iki bayt birden karşılaştırılır. Program boyutu yalnız DB satırının iki baytı kadar artar; LABEL satırı bellekte iz bırakmaz.
4.2.2Bellek yerleşimi
Bir değişkeni tanımlamak, assembler'dan bellekte yer istemektir. Bu yerin tam olarak neresi olduğunu bilmek, programı izlerken ve hata ayıklarken vazgeçilmezdir. Bir bellek dökümünde hangi baytın hangi değişkene ait olduğunu, bir komutun makine kodunda hangi sayının bir adres olduğunu ancak yerleşimi bilen biri okuyabilir. Bu kesim küçük bir programın baştan sona nasıl bayta dönüştüğünü izler.
4.2.2.1Örnek program ve bayt görüntüsü
ORG 100h
MOV AL, var1 ; AL ← var1
MOV BX, var2 ; BX ← var2
RET ; programı bitir
VAR1 DB 7 ; BYTE, 7
var2 DW 1234h ; WORD, 1234hProgramın düzeni bilinçli seçilmiştir: önce kod, sonra veriler. Değişkenler RET'ten sonra durduğu için işlemci onlara hiç ulaşmaz. Yürütme RET'te biter; arkadaki baytlar yalnız veri olarak okunur. Kaynakta değişkenin adı bir yerde VAR1, bir yerde var1 diye yazılmıştır. Assembler büyük-küçük harfe duyarsız olduğu için ikisi aynı değişkendir.
VAR1, 0109h–010Ah var2. Değişken adları komutların içinde bağıl konumlara dönüşmüştür.Assembler kaynak dosyayı Şekil 4.2.4'teki 11 baytlık diziye çevirir. İlk komut MOV AL, var1 üç bayttır: A0 08 01. A0, AL'ye doğrudan adresten bayt yükleyen kısa işlem kodudur ve ardından gelen iki bayt adresin kendisidir: 0108h. İkinci komut MOV BX, var2 dört bayttır: 8B 1E 09 01. 8B işlem kodu “sözcük yazmacına bellekten yükle” anlamındadır, 1E baytı (ModR/M) hedefin BX olduğunu ve adresin komutta doğrudan verildiğini söyler, 09 01 de 0109h'dir. Üçüncü komut tek baytlık C3'tür (RET). Ardından veri gelir: 0108h'de 07, 0109h'de 34 ve 010Ah'de 12.
Bu görüntüde dikkat çeken iki nokta vardır. Birincisi, makine kodunda hiçbir ad yoktur; var1 ve var2 yerini 0108h ve 0109h sayılarına bırakmıştır. İkincisi, adresler bile little-endian yazılır: 0108h, komutun içinde 08 01 sırasıyla durur. Little-endian düzen yalnız veriye değil, komutun içine gömülen her 16 bitlik sayıya uygulanır.
Little-endian düzende çok baytlı bir değerin düşük baytı düşük adrese, yüksek baytı bir sonraki adrese yazılır. 8086 little-endian'dır: DW 1234h bellekte 34 12, DD 12345678h ise 78 56 34 12 olarak durur.
Bu düzen 8086'nın donanımıyla uyumludur. 16 bitlik veri yolunun düşük yarısı (D0–D7) çift adresli baytı, yüksek yarısı (D8–D15) tek adresli baytı taşır. Çift adresten başlayan bir sözcük okunduğunda düşük bayt kendiliğinden yazmacın düşük yarısına, yüksek bayt yüksek yarısına gelir. Hiçbir bayt takası gerekmez.
4.2.2.2Kod izleme: var1 ve var2
Programı adım adım izlerken IP'nin her komuttan sonra komutun uzunluğu kadar ilerlediğini görmek, bayt görüntüsünü doğrulamanın en kolay yoludur.
| Satır | Makine kodu | IP | AL | BX | Açıklama |
|---|---|---|---|---|---|
1 ORG 100h | — | 0100h | 00h | 0000h | yükleme sonrası başlangıç |
2 MOV AL, var1 | A0 08 01 | 0103h | 07h | 0000h | [0108h] okunur |
3 MOV BX, var2 | 8B 1E 09 01 | 0107h | 07h | 1234h | [0109h] ve [010Ah] okunur |
4 RET | C3 | 0000h | 07h | 1234h | yığından 0000h alınır; PSP'deki INT 20h |
MOV AL, [0108h] sonrası AL = 07h, IP = 0103h. Sağda MOV BX, [0109h] sonrası: 34h düşük, 12h yüksek bayta gider, BX = 1234h, IP = 0107h.Birinci adımda işlemci 0100h'deki üç baytı yürütür. BIU fiziksel adresi DS × 10h + 0108h ile hesaplar ve tek bir okuma yol döngüsüyle 07h'yi getirir. AL = 07h, IP = 0103h olur. İkinci adımda adres 0109h'dir ve tektir. Sözcüğün düşük baytı 0109h'de, yüksek baytı 010Ah'dedir. Bu iki bayt veri yolunun aynı yarısına düşmez. 8086 önce 0109h'yi yüksek yarıdan, sonra 010Ah'yi düşük yarıdan okur ve iki baytı BX'te doğru sıraya dizer. Sonuç BX = 1234h'dir; IP dört bayt ilerleyerek 0107h olur.
Son komut RET'tir. .COM programı başlarken DOS yığına 0000h değerini koyar: SP = FFFEh, [FFFEh] = 0000h. RET bu sözcüğü IP'ye alır ve SP'yi 2 artırır: IP = 0000h, SP = 0000h. Kesimin 0000h bağıl konumunda programın önündeki PSP başlığı vardır ve onun ilk iki baytı CD 20, yani INT 20h'dir. Bu kesme programı sonlandırır ve denetimi DOS'a geri verir. İzleme tablosunda RET satırında IP'nin 0108h değil 0000h olması bu yüzdendir. 0108h'deki veri baytı hiçbir zaman komut olarak yürütülmez.
Sunumdaki izleme programın 0B56h kesimine yüklendiğini varsayar: DS = CS = 0B56h, VAR1'in tam adresi 0B56:0108'dir. emu8086 ise .COM programlarını 0700h kesimine yükler; aynı değişkenin tam adresi orada 0700:0108 olur. İki gösterim çelişmez. Kesim değeri DOS'un programı belleğin neresine yüklediğine bağlıdır ve her çalıştırmada değişebilir. Bağıl konumlar ise derleme anında sabittir. Program yalnız bağıl konumları kullandığı için nereye yüklenirse yüklensin aynı çalışır.
4.2.2.3Assembler ne yapar?
Assembler kaynak kodu bir bayt dizisine çevirirken iki işi birlikte yürütür. Komutları makine koduna dönüştürür ve değişken adlarını bağıl konumlarla değiştirir. Bunu yapabilmek için kaynak dosyayı baştan sona okurken bir sayaç tutar.
Konum sayacı (location counter), assembler'ın bir sonraki baytın yerleşeceği bağıl konumu tuttuğu sayaçtır. ORG onu belirli bir değere ayarlar; her komut ve her veri tanımı onu ürettiği bayt sayısı kadar artırır. Kaynakta $ simgesiyle okunur.
Örnek programda ORG 100h sayacı 0100h yapar. MOV AL, var1 üç bayt ürettiği için sayaç 0103h olur; MOV BX, var2 dört bayt üretir, sayaç 0107h'dir; RET'ten sonra 0108h'dir. Assembler VAR1 tanımına geldiğinde sayacın o anki değerini, 0108h'yi, bu adın adresi olarak sembol tablosuna yazar. DB 7 bir bayt ürettiği için sayaç 0109h olur ve var2 bu adresi alır.
Burada bir sorun vardır. MOV AL, var1 satırı, VAR1 tanımından önce gelir. Assembler bu satıra ulaştığında VAR1'in adresini henüz bilmez. Çözüm kaynağı iki kez okumaktır. Birinci geçişte yalnız her satırın kaç bayt tutacağı hesaplanır ve sembol tablosu doldurulur. İkinci geçişte, bütün adresler bilindiği için makine kodu üretilir. Bu yüzden değişkenler kaynakta kullanıldıkları yerden sonra tanımlanabilir.
Bir değişkenin bağıl konumu, kesimin başlangıcından itibaren bayt cinsinden uzaklığıdır. VAR1'in bağıl konumu 0108h'dir. Tam (mantıksal) adres kesim değeriyle birlikte yazılır: 0B56:0108. Fiziksel adres ise 0B56h × 10h + 0108h = 0B560h + 0108h = 0B668h'dir. var2 0109h'dedir, tam adresi 0B56:0109'dur ve WORD türünde olduğu için 0109h ile 010Ah'yi kaplar.
.COM programları yüklendiğinde DOS dört kesim yazmacının hepsine aynı değeri verir: CS = DS = ES = SS. Bu yüzden kod ile veri aynı kesimde durur ve bir değişkene erişmek için DS'ye ayrıca değer yüklemek gerekmez. Komutun içindeki bağıl konum, DS'nin gösterdiği kesimde doğru yeri bulur.
4.2.2.4ORG 100h direktifi ve .COM programı
ORG (origin) direktifi assembler'a konum sayacının başlangıç değerini söyler. ORG 100h, programın ilk baytının kesimin 0100h bağıl konumuna yükleneceğini bildirir. Assembler değişken adlarını adreslerle değiştirirken bu değeri hesaba katar. ORG unutulursa sayaç 0000h'den başlar ve her değişken adresi gerçek yerinden 100h eksik hesaplanır. Program hata vermeden derlenir ama yanlış baytları okur. emu8086 bu yüzden ORG yazılmamış .COM kaynaklarında 100h varsayar. ORG bir direktiftir; makine koduna dönüştürülmez ve bayt üretmez.
Neden 100h? DOS bir .COM programı yüklerken kesimin ilk 256 baytına (0000h–00FFh) PSP (program segment prefix, program kesimi öneki) adlı bir başlık koyar. PSP'de programla ilgili bilgiler tutulur: 0000h'de programı bitiren INT 20h, 80h'de komut satırının uzunluğu, 81h–FFh arasında da komut satırı parametreleri bulunur. Program bu başlığın hemen arkasından, 0100h'den başlar. Yığın kesimin en üstündedir: SP = FFFEh ile başlar ve aşağı doğru büyür. Kod, veri ve yığın 64 KB'lık tek bir kesimi paylaşır (Şekil 4.2.6).
.EXE programlarının düzeni farklıdır. .EXE dosyasının kod kesimi 0000h bağıl konumundan başlar ve değişkenler için ayrı bir veri kesimi kullanılır. Kesimlerin her biri 64 KB'a kadar büyüyebildiği için büyük programlar .EXE biçiminde yazılır. Bu derste örneklerin çoğu emu8086'nın .COM şablonuyla yazılır; tek kesim, değişkenlerin adreslerini izlemeyi kolaylaştırır.
4.2.2.5Hizalama ve EVEN
Bir sözcük değişkenin hangi adreste başladığı programın doğruluğunu etkilemez, ama hızını etkiler. Nedeni 8086'nın bellek arayüzüdür.
8086'nın belleği iki bankaya bölünmüştür. Çift adresli baytlar düşük bankada durur ve veri yolunun D0–D7 hatlarına bağlıdır. Tek adresli baytlar yüksek bankada durur ve D8–D15 hatlarına bağlıdır. İşlemci hangi bankayı seçtiğini iki sinyalle bildirir: A0 adres hattı 0 ise düşük banka, BHE (bus high enable) sinyali 0 ise yüksek banka etkindir. Çift adresten başlayan bir sözcük için A0 = 0 ve BHE = 0 aynı anda verilir; iki bayt tek yol döngüsünde birlikte gelir.
Tek adresten başlayan bir sözcükte iki bayt farklı satırlardadır. 0201h'deki bayt yüksek bankada, 0202h'deki bayt bir sonraki satırın düşük bankasındadır. 8086 bunu kendiliğinden fark eder ve iki yol döngüsü yapar: önce 0201h'yi yüksek yarıdan, sonra 0202h'yi düşük yarıdan okur. Program yine doğru değeri alır; yalnız her döngü dört saat turu sürdüğü için sözcük erişimi başına 4 tur kaybedilir.
MOV AX, w komutu 8086'da 10 saat turu sürer; w tek adresteyse 14 tur sürer. Bu komut bir döngüde 1000 kez yürütülürse fark 1000 × 4 = 4000 turdur. 5 MHz'lik bir 8086'da bir tur 1 / 5 MHz = 200 ns'dir. Toplam kayıp 4000 × 200 ns = 800 000 ns = 0,8 ms eder.
Hizalı yerleşimi sağlamak için MASM ve TASM'da EVEN direktifi kullanılır. EVEN, konum sayacı tekse onu bir artırarak çift sayıya yuvarlar ve araya bir dolgu baytı koyar; sayaç zaten çiftse hiçbir şey yapmaz. Dolgu kod kesiminde genellikle 90h (NOP) baytıdır. Tanımlar yer değiştirmez, yalnız kayar.
a DB 5 ; 0200h
EVEN ; 0201h: dolgu
w DW 1234h ; 0202h (çift)a 0200h'ye yerleşir ve sayaç 0201h olur. EVEN sayacı 0202h'ye yuvarlar ve 0201h'ye bir dolgu baytı koyar. w çift adreste başlar; bellekte 05, dolgu, 34, 12 sırası oluşur. Bayt değişkenleri için hizalama gerekmez, çünkü tek bir bayt her adreste tek yol döngüsünde okunur. Veri yolu 8 bit olan 8088'de ise her sözcük zaten iki yol döngüsü ister; orada hizalama fark yaratmaz.
4.2.2.6Değişkenler nereye yazılır?
İşlemci bellekteki baytların kod mu veri mi olduğunu ayırmaz. IP hangi baytı gösteriyorsa onu komut olarak çözer. Değişkenlerin programın neresine yazılacağı bu yüzden önemlidir: yürütme yolunun üzerine düşen veri baytları komut sanılır. Üç yaygın düzen vardır.
| Yer | Örnek | Dikkat edilecek nokta |
|---|---|---|
| Kodun sonunda | RET · x DB 5 | Yürütme RET'te biter, veriye hiç ulaşmaz. .COM'da en sık kullanılan düzen. |
| Kodun başında | JMP basla · x DB 5 · basla: | JMP veriyi atlar; unutulursa veri baytları komut olarak yürütülür. |
| Ayrı veri kesimi (.EXE) | .DATA · x DB 5 | Program başında MOV AX, @data · MOV DS, AX şarttır. |
İlk düzen bu notun programlarının çoğunda kullanılır. İkinci düzende veriler programın başındadır ve ilk komut bir JMP'tir. Bu düzenin bir yararı vardır: değişkenlerin adresleri koddaki değişikliklerden etkilenmez. Kod uzayıp kısalsa da JMP'in hemen arkasındaki veriler hep aynı bağıl konumda kalır. Örneğin 2 baytlık kısa JMP'ten sonra ilk değişken her zaman 0102h'dedir. JMP unutulursa işlemci 0100h'deki veri baytlarını komut olarak çözer. Örneğin DW 10'un baytları 0A 00, OR AL, [BX+SI] olarak yürütülür; program hata vermeden anlamsız işler yapar.
Üçüncü düzen .EXE programlarına özgüdür. Kod CS'nin gösterdiği kesimde, veri DS'nin gösterdiği kesimde durur. Değişkenlerin bağıl konumları veri kesiminin başına göre hesaplanır. Program başladığında DOS, DS'yi programın verisine değil PSP'ye ayarlar. Bu yüzden .EXE programının ilk iki komutu DS'yi yükler. @data veri kesiminin kesim değeridir. Kesim yazmacına anlık değer yüklenemediği için değer önce AX'e alınır.
.EXE programında MOV AX, @data · MOV DS, AX satırlarını unutmak. MOV AL, x doğru bağıl konumu kullanır ama yanlış kesimden, PSP'nin bulunduğu kesimden okur. Assembler da işlemci de hata vermez; program yalnızca yanlış değer alır. .COM programlarında CS = DS olduğu için bu sorun doğmaz.
4.2.3Diziler
Tek tek değişkenler küçük programlar için yeterlidir. Ama bir sensörden okunan yüz ölçüm, bir metnin harfleri ya da bir dönüşüm tablosu için yüz ayrı ad tanımlamak hem olanaksız hem de anlamsızdır. Bu tür veriler aynı türden ardışık baytlar olarak saklanır ve tek bir adla anılır: dizi.
4.2.3.1Dizi kavramı
Dizi (array), bellekte ardışık konumlara yerleşmiş aynı türden öğelerdir. Dizinin adı ilk öğenin adresini temsil eder; sonraki öğeler onun hemen ardından gelir. Bir öğeye, dizinin adresine öğenin bayt uzaklığı eklenerek ulaşılır.
Bir dizi değişken zinciri gibi düşünülebilir. DB ya da DW direktifinden sonra virgülle ayrılmış birden çok değer yazılırsa assembler hepsini sırayla, aralarında boşluk bırakmadan yerleştirir. Tırnak içindeki bir metin de kendiliğinden bir bayt dizisine dönüşür: her karakter yerine ASCII kodu yazılır.
a DB 48h, 65h, 6Ch, 6Ch, 6Fh, 00h
b DB 'Hello', 0a ve b dizileri bellekte art arda durur. İki tanım aynı baytları üretir: 48h 'H', 65h 'e', 6Ch 'l', 6Ch 'l', 6Fh 'o' ve dizgi sonu 00h.Şekil 4.2.8'deki iki tanım farklı yazılmıştır ama bellekte aynı altı baytı üretir. a sayıları onaltılık olarak verir; b ise metni tırnak içinde yazar ve assembler her harfi ASCII koduna çevirir. Sondaki 0 bir sonlandırıcıdır ve dizginin nerede bittiğini gösterir. Bellek açısından “metin” ile “sayı dizisi” arasında hiçbir fark yoktur; ikisi de bayt dizisidir.
4.2.3.2Kod izleme: dizi elemanlarına erişim
MOV AL, a[3] ; a + 3
MOV SI, 0
MOV BL, a[SI] ; a + 0
INC SI
MOV BH, a[SI] ; a + 1
MOV a[SI], 'E' ; a[1] ← 45h
RET
a DB 48h,65h,6Ch,6Ch,6Fh,0Programda ORG yazılmamıştır; emu8086 bu durumda .COM programı varsayar ve kodu 0100h'den başlatır. Yedi komut 21 bayt tutar; a dizisi 0115h'den başlar. Bir dizi elemanına iki yolla erişilir: sabit bir indisle ya da bir yazmaçla.
MOV AL, a[3] sabit indisli erişimdir. Köşeli parantez içindeki sayı eleman numarası değil, bayt uzaklığıdır: a[3], “a'nın adresine 3 ekle” demektir. Uzaklık derleme anında bilindiği için assembler toplamayı kendisi yapar: 0115h + 3 = 0118h. Komut, doğrudan adresli MOV AL, [0118h]'e dönüşür ve A0 18 01 olarak kodlanır. 0118h'deki bayt 6Ch'dir; AL = 6Ch, yani 'l' olur. İşlemci çalışırken hiçbir toplama yapmaz.
MOV SI, 0 indis yazmacını sıfırlar. MOV BL, a[SI] yazmaçlı erişimdir: adres çalışma anında a + SI olarak hesaplanır. Bu kez makine kodu 8A 9C 15 01'dir. ModR/M baytı 9Ch, adresin “SI + 16 bitlik yer değiştirme” olduğunu söyler; yer değiştirme de dizinin adresi 0115h'dir. EU etkin adresi 0115h + SI = 0115h olarak hesaplar ve BIU o baytı okur: BL = 48h ('H').
INC SI indisi bir artırır; SI = 1 artık ikinci elemanı gösterir. Aynı komut MOV BH, a[SI] bu kez 0116h'yi okur: BH = 65h ('e'). BX = 6548h olur. Burada döngünün temel fikri görülür: komut aynı kalır, yalnız indis yazmacı değişir. Bir döngü SI'yi her turda bir artırarak bütün diziyi aynı komutla tarayabilir.
MOV a[SI], 'E' diziye yazar. Hedef a BYTE türünde olduğu için boyut belirsizliği yoktur; assembler C6 84 15 01 45 komutunu üretir ve tek bayt yazılır: [0116h] = 45h. Dizi artık “HEllo”dur. 'E' tırnaklı bir karakter sabitidir ve assembler için 45h sayısından farksızdır.
| Satır | Etkin adres | AL | SI | BX | Dizi |
|---|---|---|---|---|---|
1 MOV AL, a[3] | 0118h | 6Ch | 0000h | 0000h | H e l l o 0 |
2 MOV SI, 0 | — | 6Ch | 0000h | 0000h | H e l l o 0 |
3 MOV BL, a[SI] | 0115h | 6Ch | 0000h | 0048h | H e l l o 0 |
4 INC SI | — | 6Ch | 0001h | 0048h | H e l l o 0 |
5 MOV BH, a[SI] | 0116h | 6Ch | 0001h | 6548h | H e l l o 0 |
6 MOV a[SI], 'E' | 0116h | 6Ch | 0001h | 6548h | H E l l o 0 |
a[3] = a + 3 bayt, AL = 6Ch ('l'). Sağda son yazma: a[1] ← 45h; dizi “HEllo” olur.İki erişim yolu donanımda farklı maliyetlere yol açar. MOV AL, [0118h] 10 saat turu sürer. MOV BL, a[SI] ise etkin adresi “yer değiştirme + indis” olarak hesaplar; bu hesap 8086'da 9 tur ekler ve komut 8 + 9 = 17 tur sürer. Sabit indisli erişim daha hızlıdır ama esnek değildir; indis yazmacı her turda farklı elemana erişmeyi sağlar.
4.2.3.3Erişim yolları ve DUP
Bir dizi elemanına erişimin bütün biçimleri aynı etkin adres hesabına iner. 8086'da köşeli parantez içinde yalnız BX, BP, SI ve DI kullanılabilir; bu dört yazmaç bellek indis yazmacı olarak çalışabilir. MOV SI, 3 · MOV AL, a[SI] ile MOV AL, a[3] aynı baytı okur. Assembler şu yazımları eşdeğer kabul eder:
a[SI] = [a + SI] = [SI + a] = [SI]+aHangisi yazılırsa yazılsın makine kodu aynıdır: adres, indis yazmacı ile 16 bitlik yer değiştirmenin toplamıdır. İndis her zaman bayt cinsindendir. Bayt dizisinde i. elemanın uzaklığı i'dir, ama sözcük dizisinde 2 × i'dir. C gibi yüksek düzeyli dillerin aksine assembler eleman boyuyla çarpmayı programcının yerine yapmaz.
Büyük diziler için her değeri tek tek yazmak gerekmez. DUP (duplicate) işleci bir değer listesini istenen sayıda tekrarlar.
sayı DUP(değer(ler)) ifadesi, parantez içindeki değer listesini sayı kez art arda yazar. Parantez içinde ? yazılırsa ilk değersiz alan ayrılır; DUP iç içe kullanılabilir.
c DB 5 DUP(9),c DB 9, 9, 9, 9, 9ile aynıdır: 5 bayt.d DB 5 DUP(1, 2),d DB 1, 2, 1, 2, 1, 2, 1, 2, 1, 2ile aynıdır: 10 bayt. Tekrarlanan şey tek bir değer değil, bütün listedir.tampon DB 100 DUP(?)100 baytlık ilk değersiz bir alan ayırır. Klavyeden okunan bir satır ya da bir hesap sonucu için yer açmanın olağan yoludur.tablo DW 10 DUP(0)10 sözcük, yani 20 bayt ayırır ve hepsini sıfırlar.
DUP ile üretilen baytlar: 5 DUP(9) beş tane 09, 5 DUP(1, 2) on bayt: 01 02 çifti beş kez.DW ile de dizi tanımlanır: w DW 1000h, 2000h iki sözcüklük bir sayı dizisidir. 255'ten büyük ya da −128'den küçük değerler bir bayta sığmadığı için bu tür sayılar DW ister. DW'nin kullanılamadığı yer uzun metinlerdir: DW 'Hello' geçersizdir. Bir sözcük en çok iki karakter alabilir; beş karakterli bir metni sözcüklere bölmenin tek anlamlı bir yolu yoktur. Metinler her zaman DB ile tanımlanır.
4.2.3.4Sözcük dizileri
Sözcük dizisinde her eleman iki bayt kaplar. Bu durumda eleman numarası ile bayt uzaklığı birbirinden ayrılır ve en sık yapılan dizi hatası tam bu noktada ortaya çıkar.
MOV AX, w[0] ; 1. eleman
MOV BX, w[2] ; 2. eleman
MOV SI, 4
MOV CX, w[SI] ; 3. eleman
MOV DX, w[1] ; dikkat!
RET
w DW 1111h, 2222h, 3333hKod 0100h'den başlar ve 19 bayt tutar; w 0113h'dedir. Bellekte 0113h'den itibaren 11 11 22 22 33 33 baytları durur. MOV AX, w[0] ilk sözcüğü okur: AX = 1111h. İkinci eleman w[1]'de değil w[2]'dedir, çünkü ilk eleman iki bayt kaplar: BX = 2222h. Genel kural, i. elemanın (0'dan sayarak) uzaklığının 2 × i olmasıdır. Üçüncü elemanın indisi 2 × 2 = 4'tür: MOV SI, 4 · MOV CX, w[SI] ile CX = 3333h olur.
Son satır MOV DX, w[1] hatalıdır ama assembler bunu fark etmez. Uzaklık 1, birinci elemanın ortasıdır. Komut 0114h'den başlayan sözcüğü okur: düşük bayt 0114h'deki 11h, yüksek bayt 0115h'deki 22h. DX = 2211h olur. Bu değer dizinin hiçbir elemanı değildir; iki komşu elemanın birer yarısından oluşur. Aynı biçimde w[3] de 2222h'nin yüksek baytı ile 3333h'nin düşük baytını birleştirir ve 3322h verir. Sözcük dizisinde indisin hep çift olması gerekir.
| Satır | Okunan adresler | AX | BX | CX | DX | SI |
|---|---|---|---|---|---|---|
1 MOV AX, w[0] | 0113h, 0114h | 1111h | 0000h | 0000h | 0000h | 0000h |
2 MOV BX, w[2] | 0115h, 0116h | 1111h | 2222h | 0000h | 0000h | 0000h |
3 MOV SI, 4 | — | 1111h | 2222h | 0000h | 0000h | 0004h |
4 MOV CX, w[SI] | 0117h, 0118h | 1111h | 2222h | 3333h | 0000h | 0004h |
5 MOV DX, w[1] | 0114h, 0115h | 1111h | 2222h | 3333h | 2211h | 0004h |
w[0]: AX = 1111h. Sağda w[1]: birinci elemanın yüksek baytı ile ikinci elemanın düşük baytı birleşir, DX = 2211h.Tablo 4.2.9'de w'nin tek adreste (0113h) başladığına dikkat etmek gerekir. Bu yerleşimde w[0], w[2] ve w[4]'ün her biri tek adresli bir sözcüktür ve her okuma iki yol döngüsü ister. DW satırından önce bir EVEN yazılsaydı dizi 0114h'den başlar ve her eleman tek yol döngüsünde okunurdu. Bu durumda yanlış indisli w[1] ise tek adrese düşerdi.
Sözcük dizisinde i. elemana w[i] ile erişmeye çalışmak. Üçüncü eleman w[2]'de değil w[4]'tedir. Döngülerde indis yazmacı her turda 1 değil 2 artırılır: ADD SI, 2.
4.2.3.5Tanımdan bayta
Bir veri tanımının bellekte hangi baytları ürettiğini kâğıt üzerinde çıkarabilmek, bellek dökümlerini okumak için gereklidir. Kural her zaman aynıdır: değerler soldan sağa sırayla yazılır, her değer türünün boyu kadar yer kaplar ve çok baytlı değerler little-endian düzende durur. Tablo 4.2.10 sık karşılaşılan tanımların açılımını verir.
| Tanım | Baytlar | Boyut |
|---|---|---|
b DB 'Hello', 0 | 48 65 6C 6C 6F 00 | 6 bayt |
a DB 48h, 65h, 6Ch, 6Ch, 6Fh, 00h | 48 65 6C 6C 6F 00 | 6 bayt |
var2 DW 1234h | 34 12 | 2 bayt |
d DB 5 DUP(1, 2) | 01 02 01 02 01 02 01 02 01 02 | 10 bayt |
w DW 3 DUP(?), -1 | ?? ?? ?? ?? ?? ?? FF FF | 8 bayt |
mydouble DD 12345678h | 78 56 34 12 | 4 bayt |
x DB 300 | hata: 300 bir bayta sığmaz | — |
w DW 3 DUP(?), -1 satırı iki parçalıdır. Önce üç ilk değersiz sözcük, yani altı bayt ayrılır; emu8086 bunlara 00 yazar. Ardından −1 bir sözcük olarak eklenir. −1'in 16 bitlik ikiye tümleyeni FFFFh'dir ve little-endian sırayla FF FF olarak yazılır. Satırın toplam boyu 8 bayttır. Tanımın tek bir adı olduğu hâlde dört elemanı vardır.
4.2.3.6Dizgiler ve sonlandırıcılar
Dizgi (string), bellekte ardışık karakter kodlarından oluşan metindir. Dizginin bittiği yeri bir sonlandırıcı bayt (0 ya da '$') ya da başta duran bir uzunluk baytı gösterir.
Bellek bir dizginin nerede bittiğini bilmez. Bir dizgiyi ekrana yazan ya da kopyalayan program, bitiş noktasını bir kurala göre bulmak zorundadır. Üç yaygın kural vardır:
s1 DB 'Merhaba', 0 ; sıfırla biten dizgi
s2 DB 'Merhaba$' ; '$' ile biten (INT 21h / AH = 09h)
s3 DB 13, 10, 'Satır', 13, 10, '$' ; CR LF: satır başı + yeni satır
s4 DB 7, 'Merhaba' ; uzunluk önekli: ilk bayt = 7
s5 DB "Ali'nin", 0 ; çift tırnak içinde tek tırnak| Biçim | Sonu nasıl anlaşılır? | Nerede kullanılır? |
|---|---|---|
| 0 ile biten | okunan bayt 0 olunca | emu8086.inc PRINT_STRING, C dili |
| '$' ile biten | okunan bayt 24h olunca | DOS dizgi yazdırma hizmeti |
| uzunluk önekli | ilk bayttaki sayı kadar | DOS satır girişi tamponu (AH = 0Ah) |
Dizginin bellekte kapladığı yer, karakter sayısı ile sonlandırıcının toplamıdır. s1 yedi harf ve bir sıfır, yani 8 bayttır. s3'teki 13 ve 10 sayıları karakter değil denetim kodlarıdır: 13 (0Dh) satır başı (CR), 10 (0Ah) yeni satırdır (LF). DOS'ta bir satırı bitirip alttaki satırın başına geçmek için ikisi birlikte yazılır. s4 uzunluk önekli biçimdir: ilk bayt 7, ardından yedi karakter gelir. Bu biçimde sonlandırıcı gerekmez ve dizginin uzunluğu tek bir okumayla öğrenilir. s5 tek tırnak içeren bir metni çift tırnakla yazar; assembler iki tırnak türünü de kabul eder.
Sonlandırıcı unutulursa dizgiyi yazan kod durmaz. Dizginin ardındaki baytları da karakter sanır ve bir 0 ya da '$' bulana kadar ekrana anlamsız işaretler basar. '$' ile biten bir dizgi '$' karakterinin kendisini yazdıramaz, çünkü onu görür görmez durur. Türkçe harflerin (ç, ğ, ı, ö, ş, ü) kodu ise kullanılan kod sayfasına bağlıdır. Bu harfler ASCII'nin 0–127 aralığında yer almaz; aynı bayt farklı bir kod sayfasında başka bir harf olarak görünebilir.
4.2.3.7Karakter sabitleri ve ASCII aritmetiği
Tırnak içindeki karakter assembler için yalnız bir sayıdır. MOV AL, 'A' ile MOV AL, 41h aynı makine kodunu (B0 41) üretir. ASCII tablosunun düzeni, karakterler üzerinde basit aritmetik yapmayı mümkün kılar.
| Karakterler | ASCII kodları |
|---|---|
| '0' … '9' | 30h … 39h |
| 'A' … 'Z' | 41h … 5Ah |
| 'a' … 'z' | 61h … 7Ah |
| boşluk · '$' | 20h · 24h |
| CR · LF | 0Dh · 0Ah |
Rakam karakterleri 30h'den başlar ve sırayla dizilir. Bu yüzden bir rakam karakterinden sayı değerini elde etmek için '0' çıkarmak yeterlidir: '7' = 37h, 37h − 30h = 7. SUB AL, '0' bu işi yapar. Ters yönde, 0–9 arasındaki bir sayıyı ekrana yazmak için ADD AL, '0' kullanılır. Klavyeden okunan her rakam bir ASCII kodudur; hesaba girmeden önce bu dönüşümden geçer.
Büyük ve küçük harfler arasındaki fark 20h'dir: 'a' = 61h, 'A' = 41h. 20h ikili olarak 0010 0000'dır; yani iki harf yalnız bit 5'te ayrılır. Küçük harfte bit 5 = 1, büyük harfte 0'dır. Bu yüzden harf büyütmek için bit 5'i silmek yeter: SUB AL, 20h ya da AND AL, 0DFh. 0DFh = 1101 1111 maskesi bit 5 dışındaki bütün bitleri korur. Örneğin 'q' = 71h, 71h AND DFh = 51h = 'Q' olur. Harf küçültmek için bit 5 kurulur: OR AL, 20h; 'Q' = 51h, 51h OR 20h = 71h = 'q'.
AND AL, 0DFh'yi harf olup olmadığını denetlemeden bir dizginin bütün baytlarına uygulamak. Rakamlarda da bit 5 = 1'dir: '1' = 31h, 31h AND DFh = 11h olur ve rakam bir denetim koduna dönüşür. Dönüşümden önce 'a' ≤ AL ≤ 'z' koşulu sınanır.
4.2.3.8Kod izleme: dizgiyi büyük harfe çevirme
Aşağıdaki program sıfırla biten bir dizgiyi yerinde büyük harfe çevirir. Uzunluğu bilmez; sonlandırıcıyı görünce durur. Harf denetimi rakamların bozulmasını önler.
ORG 100h
LEA SI, s
don:
MOV AL, [SI]
CMP AL, 0 ; sonlandırıcı?
JE bitti
CMP AL, 'a'
JB sonraki
CMP AL, 'z'
JA sonraki
SUB AL, 20h ; 'a' - 'A'
MOV [SI], AL
sonraki:
INC SI
JMP don
bitti:
RET
s DB 'Ab1c', 0Kod 25 bayttır ve 0100h–0118h arasını kaplar; dizgi 0119h'den başlar. LEA SI, s dizginin adresini SI'ye yükler: SI = 0119h. SI bundan sonra işlenen karakteri gösteren bir işaretçidir. Döngünün gövdesi her turda aynı sırayı izler:
MOV AL, [SI]sıradaki baytı okur.CMP AL, 0veJE bitti: bayt 0 ise ZF = 1 olur ve döngü biter.CMP AL, 'a'veJB sonraki: AL < 61h ise (CF = 1) bayt küçük harf değildir; yazmadan geçilir.CMP AL, 'z'veJA sonraki: AL > 7Ah ise yine geçilir.- Bu iki denetimden geçen bayt küçük harftir:
SUB AL, 20honu büyütür,MOV [SI], ALaynı adrese geri yazar. INC SIsonraki bayta geçer,JMP donbaşa döner.
Karşılaştırmalar işaretsiz atlamalarla (JB, JA) yapılır, çünkü ASCII kodları işaretsiz sayılardır. 'A' = 41h ile 'a' = 61h karşılaştırıldığında 41h − 61h işlemi ödünç ister ve CF = 1 olur; JB atlar.
| Tur | SI | AL (okunan) | Karar | Yazılan | Dizgi sonra |
|---|---|---|---|---|---|
| 1 | 0119h | 41h 'A' | 41h < 61h: JB atlar | — | A b 1 c 0 |
| 2 | 011Ah | 62h 'b' | 61h ≤ 62h ≤ 7Ah: harf | 42h 'B' | A B 1 c 0 |
| 3 | 011Bh | 31h '1' | 31h < 61h: JB atlar | — | A B 1 c 0 |
| 4 | 011Ch | 63h 'c' | harf | 43h 'C' | A B 1 C 0 |
| 5 | 011Dh | 00h | ZF = 1: JE bitti | — | A B 1 C 0 |
CMP AL, 0 ZF = 1 yapar ve döngü biter.Program sonunda SI = 011Dh, AL = 00h ve ZF = 1'dir; dizgi bellekte “AB1C” olmuştur. Döngü beş kez okudu ama yalnız iki kez yazdı. Program dizginin uzunluğunu hiçbir yerde kullanmadı; sonlandırıcı, sayaç yerine durma koşulu oldu. Dizgiye karakter eklense de çıkarılsa da kod değişmeden çalışır. Rakam '1' harf denetimine takıldığı için korundu; denetim olmasaydı 31h − 20h = 11h ile bozulurdu.
4.2.3.9İki boyutlu diziler
Bir matris ya da bir tablo, satırları ve sütunları olan iki boyutlu bir yapıdır. Bellek ise tek boyutludur. İki boyutlu bir dizi belleğe yerleştirilirken satırlar art arda dizilir.
Satır öncelikli düzende (row-major order) önce 0. satırın bütün sütunları, sonra 1. satırın bütün sütunları yerleşir. S sütunlu bir tabloda [satır][sütun] elemanının tablonun başından bayt uzaklığı (satır × S + sütun) × eleman boyudur.
mat DB 3 DUP(4 DUP(0)) ; 3 × 4
tab DW 3 DUP(4 DUP(0)) ; 24 baytİç içe DUP satır ve sütunu birlikte ayırır: “3 kez (4 kez 0)”. mat 12 bayt, tab 12 sözcük, yani 24 bayttır. Bellekte iç içe yapıdan iz kalmaz; yalnız 12 ya da 24 ardışık bayt vardır. Tabloyu tablo yapan, programın uzaklığı hesaplarken kullandığı formüldür.
Şekil 4.2.13'daki bayt tablosunda [2][1] elemanının uzaklığı 2 × 4 + 1 = 9'dur. Sözcük tablosunda aynı hesap 2 ile çarpılır. Örneğin tab'ın [1][2] elemanı (1 × 4 + 2) × 2 = 12. bayttan başlar. Formülde satır sayısı geçmez. Satır sayısı yerleşimi etkilemez, yalnız satır indisinin alabileceği en büyük değeri belirler.
Sütun sayısı 2'nin bir kuvvetiyse çarpma yerine kaydırma kullanılabilir. Dört sütun için satır × 4, satırı iki kez sola kaydırmakla aynıdır: SHL AL, 1 iki kez. 8086'da 8 bitlik bir MUL 70–77 saat turu sürerken her SHL 2 tur sürer. Tablo boyutlarının çoğu zaman 2'nin kuvveti seçilmesi bu yüzdendir.
4.2.3.10Kod izleme: tablo elemanını okuma
SUTSAY EQU 4
ORG 100h
MOV AL, satir ; AL = 2
MOV BL, SUTSAY ; BL = 4
MUL BL ; AX = 2 × 4
ADD AL, sutun ; + 1
MOV SI, AX ; SI = 9
MOV DL, mat[SI] ; mat[2][1]
RET
mat DB 11h, 12h, 13h, 14h
DB 21h, 22h, 23h, 24h
DB 31h, 32h, 33h, 34h
satir DB 2
sutun DB 1Tablonun elemanları satır ve sütun numarasını taşıyacak biçimde seçilmiştir: 32h, 3. satırın (indis 2) 2. sütunudur (indis 1). Kod 0100h–0111h arasını kaplar; mat 0112h'de, satir 011Eh'de, sutun 011Fh'dedir. Yalnız ilk satır adlandırılmıştır. Sonraki iki DB satırı adsızdır ve ilk satırın hemen arkasına yerleşir.
MOV AL, satir satır indisini bellekten alır: AL = 2. MOV BL, SUTSAY bir sabit kullanır. SUTSAY EQU 4 bellekte yer kaplamaz; assembler bu satırı MOV BL, 4 (B3 04) olarak kodlar. MUL BL AL ile BL'yi işaretsiz çarpar ve 16 bitlik sonucu AX'e yazar: AX = 2 × 4 = 0008h. Bu, 2. satırın tablodaki başlangıç uzaklığıdır. Sonucun üst yarısı (AH) 0 olduğu için CF = OF = 0 olur.
ADD AL, sutun sütun indisini ekler: 8 + 1 = 9. AH zaten 0 olduğu için AX = 0009h'dir. Uzaklık bir adres yazmacına alınmalıdır, çünkü 8086'da AL ya da AX adreslemede kullanılamaz; köşeli parantez içinde yalnız BX, SI, DI ve BP olabilir. MOV SI, AX ile SI = 0009h olur. Son komut MOV DL, mat[SI] etkin adresi 0112h + 9 = 011Bh olarak hesaplar ve oradaki baytı okur: DL = 32h.
| Satır | AX | BL | SI | DL | Açıklama |
|---|---|---|---|---|---|
3 MOV AL, satir | 0002h | 00h | 0000h | 00h | satır indisi |
4 MOV BL, SUTSAY | 0002h | 04h | 0000h | 00h | sabit komuta gömülü |
5 MUL BL | 0008h | 04h | 0000h | 00h | 2 × 4: satırın başı |
6 ADD AL, sutun | 0009h | 04h | 0000h | 00h | + sütun |
7 MOV SI, AX | 0009h | 04h | 0009h | 00h | uzaklık indis yazmacında |
8 MOV DL, mat[SI] | 0009h | 04h | 0009h | 32h | [0112h + 9] = [011Bh] |
MUL BL sonrası AX = 0008h: 2. satır tablonun 8. baytından başlar. Sağda MOV DL, mat[SI]: 9. bayt okunur, DL = 32h.Satır ve sütun çalışma anında değişkenlerden okunduğu için aynı kod tablonun her elemanı için çalışır. Sözcük tablosunda son uzaklık ayrıca 2 ile çarpılırdı; bunun için SHL SI, 1 eklemek yeterlidir.
Sabitin adı bilinçli olarak SUTSAY seçilmiştir. SUTUN EQU 4 yazılsaydı assembler büyük-küçük harfe duyarsız olduğu için bu ad sutun DB 1 ile çakışırdı. Assembler “sembol iki kez tanımlandı” hatası verir ve derlemeyi durdurur.
4.2.4Adresler
Bir değişken iki sayıyla ilişkilidir: içinde saklanan değer ve bellekteki yeri, yani adresi. Assembly'de bir değişkenin adı komutta tek başına yazıldığında değeri temsil eder. MOV AL, VAR1, VAR1'in adresindeki baytı AL'ye getirir. Bazen ise değerin değil adresin kendisine ihtiyaç duyulur. Bir diziyi işaretçiyle taramak, yazdırma hizmetine dizginin yerini bildirmek ya da bir prosedüre büyük bir veri bloğunu kopyalamadan vermek bu durumlara örnektir. Bu kesim adresi yazmaca almanın iki yolunu, LEA komutunu ve OFFSET işlecini anlatır.
4.2.4.1Değişkenin adresini alma
LEA (load effective address) bir işlemci komutudur: bellek işleneninin etkin adresini (bağıl konumunu) hesaplar ve hedef yazmaca yükler. Belleğe erişmez. LEA BX, VAR1 BX'e VAR1'in adresini, LEA BX, a[SI] ise a + SI toplamını yükler.
OFFSET bir assembler işlecidir: bir değişkenin bağıl konumunu derleme anında bir sabit sayı olarak verir. MOV BX, OFFSET VAR1, assembler'ın gözünde MOV BX, 010Dh gibi bir anlık değer yüklemesidir.
LEA BX, VAR1 adresi (010Dh), MOV AL, VAR1 değeri (22h) yükler.| Komut | BX'e yüklenen | Belleğe erişim |
|---|---|---|
MOV BX, VAR1 | VAR1'in değeri (içerik) | var: iki bayt okunur |
LEA BX, VAR1 | VAR1'in adresi | yok: yalnız EU'da hesap |
MOV BX, OFFSET VAR1 | VAR1'in adresi | yok: adres komutun içinde |
Bir değişkenin adresi, bir prosedüre parametre geçirmek için çok kullanışlıdır. Bin baytlık bir diziyi işleyecek bir alt programa dizinin kendisini kopyalamak hem yavaştır hem de yer harcar. Bunun yerine yalnız 16 bitlik adresi, örneğin SI'de verilir; alt program diziye bu adres üzerinden ulaşır. DOS'un dizgi yazdırma hizmeti de aynı fikri kullanır: dizginin adresi DX'te beklenir.
4.2.4.2Kod izleme: LEA ile değişken güncelleme
ORG 100h
MOV AL, VAR1 ; değer
LEA BX, VAR1 ; adres
MOV BYTE PTR [BX], 44h
MOV AL, VAR1 ; yeni değer
RET
VAR1 DB 22h
ENDProgramın bayt görüntüsü şöyledir: A0 0D 01 (3), BB 0D 01 (3), C6 07 44 (3), A0 0D 01 (3) ve C3 (1). Kod 12 bayttır ve RET 010Ch'dedir; VAR1 0100h + 13 = 010Dh adresine yerleşir. emu8086, taban ya da indis yazmacı içermeyen LEA BX, VAR1 komutunu MOV BX, OFFSET VAR1 ile aynı üç baytlık koda çevirir: BB işlem kodu ve 16 bitlik adres. Gerçek 8086 assembler'ı MASM ise aynı satırı 8D 1E 0D 01 biçiminde dört baytlık bir LEA olarak kodlar.
MOV AL, VAR1 VAR1'in değerini okur: AL = 22h. LEA BX, VAR1 ise VAR1'in adresini BX'e yükler: BX = 010Dh. BX artık VAR1'i gösteren bir işaretçidir. MOV BYTE PTR [BX], 44h bu işaretçi üzerinden yazar. [BX] türsüz bir bellek işleneni olduğu için BYTE PTR zorunludur. Komut DS:010Dh'ye 44h yazar. Son satırdaki MOV AL, VAR1 değişkeni adıyla yeniden okur ve yeni değeri görür: AL = 44h. Aynı bellek konumuna hem adıyla hem de bir işaretçiyle erişilmiştir. END kaynak dosyanın sonunu bildiren direktiftir ve bayt üretmez.
| Satır | AL | BX | [010Dh] | Açıklama |
|---|---|---|---|---|
2 MOV AL, VAR1 | 22h | 0000h | 22h | değer okunur |
3 LEA BX, VAR1 | 22h | 010Dh | 22h | adres yüklenir, bellek okunmaz |
4 MOV BYTE PTR [BX], 44h | 22h | 010Dh | 44h | işaretçi üzerinden yazma |
5 MOV AL, VAR1 | 44h | 010Dh | 44h | yeni değer okunur |
LEA BX, VAR1 sonrası BX = 010Dh, bellekte 22h. Sağda MOV BYTE PTR [BX], 44h sonrası VAR1 = 44h.Donanım açısından LEA ile MOV arasındaki fark yol döngüsündedir. MOV AL, VAR1'de EU adresi BIU'ya verir, BIU bir okuma yol döngüsü yapar ve gelen bayt AL'ye yazılır. LEA BX, VAR1'de hiçbir okuma yol döngüsü olmaz: emu8086 adresi komutun içine sabit olarak koyar ve EU onu doğrudan BX'e yazar; bu biçim 4 tur sürer. Yazmaçlı bir LEA'da (LEA BX, a[SI]) EU adresi çalışma anında hesaplar ama sonucu BIU'ya göndermez, yine doğrudan yazmaca yazar; süresi 2 saat turu artı etkin adres hesabıdır.
4.2.4.3LEA ve OFFSET aynı mı?
Aynı program LEA yerine OFFSET ile de yazılabilir:
ORG 100h
MOV AL, VAR1
MOV BX, OFFSET VAR1
MOV BYTE PTR [BX], 44h
MOV AL, VAR1
RET
VAR1 DB 22h
ENDSonuç aynıdır: BX değişkenin adresini alır, değişken 44h olur, AL son olarak 44h'yi okur. MOV BX, OFFSET VAR1 sıradan bir anlık değer yüklemesidir: BB 0D 01, üç bayt. emu8086 LEA BX, VAR1 satırını da tam olarak bu koda çevirir; iki programın baytları birebir aynıdır ve VAR1 ikisinde de 010Dh'dedir. Basit bir değişken için iki yazım arasında hiçbir fark yoktur. MASM'da ise LEA BX, VAR1 dört baytlık gerçek LEA komutu olarak kodlanır (8D 1E 0D 01).
Asıl fark indisli adreslerde ortaya çıkar. OFFSET derleme anında çalışır; yalnız assembler'ın bildiği sabit adresleri verebilir. OFFSET a[SI] anlamsızdır, çünkü SI'nin değeri derleme anında bilinmez. LEA BX, a[SI] ise adresi çalışma anında, SI'nin o andaki değeriyle hesaplar. LEA SI, [BX+DI+4] gibi karmaşık ifadeler de mümkündür; LEA burada bayrakları değiştirmeyen küçük bir toplayıcı gibi çalışır.
LEA BX, VAR1 | MOV BX, OFFSET VAR1 | |
|---|---|---|
| Ne zaman hesaplanır? | indisli adreste çalışma anında, EU'da | derleme anında, assembler'da |
| Makine kodu (emu8086) | doğrudan adres: BB imm16 (3 bayt); indisli: 8D ModR/M (+ uzaklık) | BB imm16 (3 bayt) |
| Süre (8086) | doğrudan adres: 4 tur; indisli: 2 + EA | 4 tur |
| İndisli adres | olur: LEA BX, a[SI] | olmaz |
| Bayraklar | değişmez | değişmez |
Sabit bir adres için MOV ... OFFSET hem kısa hem de hızlıdır. İndisli bir adresin hesaplanması gerektiğinde LEA kullanılır. Hangisi seçilirse seçilsin köşeli parantez içinde yalnız BX, SI, DI ve BP bulunabilir.
4.2.4.4Kod izleme: LEA a[BX] ile OFFSET a
ORG 100h
MOV BX, 2
LEA SI, a[BX] ; SI = a + BX
MOV DI, OFFSET a ; sabit adres
ADD DI, BX ; elle ekle
MOV AL, [SI]
INC BX
LEA SI, a[BX] ; yeni adres
MOV AH, [SI]
RET
a DB 'ABCD'Kod 22 bayttır; a dizisi 0116h'de başlar ve 'A' 'B' 'C' 'D' (41h–44h) baytlarını tutar. MOV BX, 2 indisi hazırlar. LEA SI, a[BX] (8D B7 16 01) adresi çalışma anında hesaplar: 0116h + 2 = 0118h. Bellek okunmaz; SI yalnız 'C'nin adresini alır.
MOV DI, OFFSET a (BF 16 01) derleme anında bilinen sabiti yükler: DI = 0116h. Bu sabit BX'i içeremez. Aynı adrese ulaşmak için ayrı bir toplama gerekir: ADD DI, BX ile DI = 0118h = SI olur. İki yol aynı adresi verdi; LEA bunu tek komutta yaptı.
MOV AL, [SI] 0118h'deki baytı okur: AL = 43h ('C'). INC BX indisi 3 yapar. İkinci LEA SI, a[BX] aynı makine koduyla yeni sonucu üretir: 0116h + 3 = 0119h. Komut değişmedi, BX değiştiği için adres değişti. MOV AH, [SI] AH = 44h ('D') yapar; AX = 4443h olur. DI ise 0118h'de kalır, çünkü OFFSET'ten gelen değer BX'teki değişikliği izlemez.
| Satır | BX | SI | DI | AX |
|---|---|---|---|---|
2 MOV BX, 2 | 0002h | 0000h | 0000h | 0000h |
3 LEA SI, a[BX] | 0002h | 0118h | 0000h | 0000h |
4 MOV DI, OFFSET a | 0002h | 0118h | 0116h | 0000h |
5 ADD DI, BX | 0002h | 0118h | 0118h | 0000h |
6 MOV AL, [SI] | 0002h | 0118h | 0118h | 0043h |
7 INC BX | 0003h | 0118h | 0118h | 0043h |
8 LEA SI, a[BX] | 0003h | 0119h | 0118h | 0043h |
9 MOV AH, [SI] | 0003h | 0119h | 0118h | 4443h |
LEA SI, a[BX]: SI = 0116h + 2 = 0118h. Sağda MOV DI, OFFSET a: DI = 0116h, BX'ten habersiz sabit adres.4.2.4.5İşaretçi değişkenleri
Bir adres yalnız yazmaçta değil, bellekte de saklanabilir. DW'nin değeri bir değişken adı olursa assembler o değişkenin adresini belleğe yazar.
İşaretçi değişken (pointer variable), içinde başka bir değişkenin adresini saklayan değişkendir. p DW dizi 16 bitlik bir yakın işaretçi, uzak DD dizi ise bağıl konum ve kesimden oluşan 32 bitlik bir uzak işaretçi tanımlar.
ORG 100h
MOV BX, p ; 010Bh
LEA SI, p ; 010Fh
MOV DI, OFFSET dizi
RET
dizi DB 'KAYA' ; 010Bh
p DW dizi ; 010Fh, içi 010Bh
uzak DD dizi ; ofset + kesimKod 11 bayt sürer (8B 1E 0F 01, BE 0F 01, BF 0B 01, C3; emu8086 LEA SI, p satırını MOV SI, OFFSET p gibi üç bayta çevirir). dizi 010Bh'den başlar ve dört bayt kaplar; p 010Fh'dedir ve içinde 010Bh, yani 0B 01 baytları durur. uzak 0111h'dedir ve dört bayt tutar: önce bağıl konum (0B 01), sonra kesim değeri. emu8086'nın yükleme kesiminde bu baytlar 0B 01 00 07'dir.
| Komut | Yazmaca ne yüklenir? | Değer |
|---|---|---|
MOV BX, p | p'nin içindeki adres (dizinin adresi) | 010Bh |
LEA SI, p | p'nin kendi adresi | 010Fh |
MOV DI, OFFSET dizi | dizinin adresi, derleme anında sabit | 010Bh |
Tablo 4.2.19'deki ilk ve üçüncü satır aynı sayıyı verir ama farklı yoldan. MOV DI, OFFSET dizi sayıyı komutun içinden alır ve değişmez. MOV BX, p ise sayıyı bellekten okur. p bellekte durduğu için program onu değiştirebilir: ADD p, 2 işaretçiyi dizinin iki bayt ilerisine kaydırır. Bir işaretçiyi kullanmak için önce bir adres yazmacına almak gerekir, çünkü 8086 bellekteki bir adresle doğrudan bellek okuyamaz: MOV BX, p · MOV AL, [BX].
Uzak işaretçi farklı bir kesimdeki veriyi göstermek için kullanılır. İçindeki iki sözcük, LDS ya da LES komutlarıyla tek seferde yüklenir. LDS SI, uzak düşük sözcüğü SI'ye, yüksek sözcüğü DS'ye yazar; LES DI, uzak aynı işi DI ve ES için yapar. uzak'ta 0B 01 00 07 baytları durduğuna göre LDS SI, uzak sonrası SI = 010Bh, DS = 0700h olur; ardından MOV AL, [SI] 'K'yi (4Bh) okur.
4.2.4.6Kod izleme: bellekteki işaretçi
ORG 100h
MOV BX, p ; p'nin içi
MOV AL, [BX] ; dizi[0]
ADD p, 2 ; ilerlet
MOV BX, p
MOV AH, [BX] ; dizi[2]
RET
dizi DB 'KAYA'
p DW dizi ; adresKod 18 bayttır; dizi 0112h'de, p 0116h'dedir ve ilk değeri 0112h'dir. MOV BX, p p'nin içeriğini okur: BX = 0112h. MOV AL, [BX] işaretçinin gösterdiği baytı alır: AL = 4Bh ('K'). ADD p, 2 doğrudan belleğe yazan bir toplamadır. Assembler sabit küçük olduğu için 5 baytlık 83 06 16 01 02 biçimini seçer; 83h işlem kodu 8 bitlik sabiti işaretle 16 bite genişletir. İşlemci önce p'yi okur, 2 ekler ve sonucu geri yazar. Bu komut bir okuma ve bir yazma yol döngüsü ister. p artık 0114h'dir. BX ise eski adresi tutmaya devam eder; yazmaç ile bellekteki işaretçi birbirinden bağımsızdır.
MOV BX, p işaretçiyi yeniden yükler: BX = 0114h. MOV AH, [BX] AH = 59h ('Y') yapar; AX = 594Bh olur.
| Satır | BX | AX | p |
|---|---|---|---|
2 MOV BX, p | 0112h | 0000h | 0112h |
3 MOV AL, [BX] | 0112h | 004Bh | 0112h |
4 ADD p, 2 | 0112h | 004Bh | 0114h |
5 MOV BX, p | 0114h | 004Bh | 0114h |
6 MOV AH, [BX] | 0114h | 594Bh | 0114h |
MOV AL, [BX]: işaretçinin gösterdiği 'K' okunur. Sağda ADD p, 2: bellekteki işaretçi 0114h'yi gösterir; BX eski adreste kalır.Dizinin adresi kodda hiç yazılmadı; hangi elemanın okunacağına bellekteki işaretçi karar verdi. Yazmaçları az olan 8086'da çok sayıda dizinin konumunu tutmanın yolu budur. Her kullanımda işaretçi önce BX, SI ya da DI'ye alınır.
Değer ile adresi karıştırmak. MOV SI, dizi dizinin adresini değil, ilk sözcüğünü yükler (dizi DB ise boyut hatası verir). Adres için LEA SI, dizi ya da MOV SI, OFFSET dizi yazılır. İşaretçi değişkende ise tersi doğrudur: MOV SI, p adresi verir, LEA SI, p işaretçinin kendi yerini.
4.2.5Sabitler
Programlarda değişmeyen sayılar sık geçer: bir satırdaki karakter sayısı, bir tablonun sütun sayısı, bir portun numarası. Bu sayıları her geçtiği yere elle yazmak iki sorun doğurur. Kod okunaksızlaşır, çünkü 80'in ne anlama geldiği belli değildir. Değer değişirse bütün kullanımları bulup düzeltmek gerekir ve birinin atlanması kolaydır. Sayıyı bir değişkende tutmak bu sorunları çözer ama bellekte yer kaplar ve her kullanımda bir bellek okuması ister. Sabitler iki sorunu da bedelsiz çözer.
4.2.5.1EQU ile sabit tanımlama
Sabit (constant), ad EQU ifade biçiminde tanımlanan ve yalnız derleme sırasında var olan addır. Assembler her kullanımda adı ifadenin değeriyle değiştirir. Sabit bellekte yer kaplamaz ve tanımlandıktan sonra değiştirilemez.
k EQU 5
SATIR EQU 80
MOV AX, k ; AX = 5
MOV BX, SATIR*2 ; BX = 160
MOV CL, k+1 ; CL = 6Sabitler değişkenlere benzer bir ad taşır ama yalnız assembler'ın sembol tablosunda yaşar. MOV AX, k satırını gören assembler onu MOV AX, 5 olarak kodlar: B8 05 00. Sabitin adı da değişken adı gibi büyük-küçük harfe duyarsızdır ve başka bir adla çakışamaz. Bir ifade de verilebilir: SATIR*2 derleme anında 160 olarak hesaplanır ve komuta 160 gömülür. Programın farklı yerlerinde aynı değer kullanılıyorsa değer tek yerden, EQU satırından güncellenir.
k EQU 5 | k DB 5 | |
|---|---|---|
| Bellek | kullanmaz | 1 bayt |
MOV AL, k | AL ← 5 (anlık değer, B0 05) | AL ← [k] (bellekten, A0 + adres) |
| Değiştirilebilir mi? | hayır: MOV k, 7 hata | evet: MOV k, 7 |
| Ne zaman vardır? | yalnız derleme anında | çalışma anında, bellekte |
MOV k, 7 satırı EQU sabitinde hata verir. Assembler k'yi 5 ile değiştirdiğinde ortaya MOV 5, 7 çıkar ve bir sayının hedef olması anlamsızdır. Değişkende aynı satır geçerlidir, çünkü k bir bellek konumunu gösterir.
4.2.5.2Kod izleme: EQU komuta gömülür
k EQU 5
SATIR EQU 80
ORG 100h
MOV AX, k ; MOV AX, 5
MOV BX, SATIR*2 ; MOV BX, 160
MOV CL, k+1 ; MOV CL, 6
MOV DL, v ; bellekten
RET
v DB 5 ; 1 bayt yerİlk iki satır sembol tablosuna k = 5 ve SATIR = 80 yazar; hiçbiri bayt üretmez. Bu yüzden ORG 100h'den sonraki ilk bayt doğrudan MOV AX, k'nın kodudur. Programın bayt görüntüsü B8 05 00 · BB A0 00 · B1 06 · 8A 16 0D 01 · C3 · 05'tir. Kod 13 bayt tutar ve v 010Dh'ye yerleşir.
| Adres | Makine kodu | Komut | AX | BX | CL | DL |
|---|---|---|---|---|---|---|
| 0100h | B8 05 00 | MOV AX, 5 | 0005h | 0000h | 00h | 00h |
| 0103h | BB A0 00 | MOV BX, 160 | 0005h | 00A0h | 00h | 00h |
| 0106h | B1 06 | MOV CL, 6 | 0005h | 00A0h | 06h | 00h |
| 0108h | 8A 16 0D 01 | MOV DL, [010Dh] | 0005h | 00A0h | 06h | 05h |
MOV AX, k: sabit 5 komutun içindedir (B8 05 00). Sağda MOV DL, v: komut v'nin adresini (010Dh) taşır, değer bellekten okunur.İlk üç komutta değer komutun kendi baytlarındadır. 160 = 00A0h olduğu için BB A0 00 yazılır; k+1 = 6 da derleme anında hesaplanır ve tek baytlık B1 06 olur. İşlemci çalışırken hiçbir toplama ya da çarpma yapmaz. Anlık değer, komutla birlikte önden getirme kuyruğuna girer. EU onu kuyruktan alır ve ayrıca bir veri yol döngüsüne gerek kalmaz. Yazmaca anlık değer yüklemek 8086'da 4 saat turu sürer.
Dördüncü komut farklıdır. v bir değişkendir; komut değeri değil adresi taşır (0D 01). EU etkin adresi verir, BIU DS:010Dh'den bir okuma yol döngüsü yapar ve DL = 05h olur. Bu komut 8 + 6 = 14 tur sürer. v bellekte bir bayt yer kaplar, k ve SATIR hiç kaplamaz.
MASM ve emu8086'da = direktifi de sabit tanımlar: sayac = 3. Farkı, = ile tanımlanan bir adın kaynakta daha sonra yeniden tanımlanabilmesidir. Bu, derleme anında sayaç gibi kullanılan adlar için uygundur. Program çalışırken ikisi de bellekte yer kaplamaz ve değiştirilemez.
4.2.5.3Konum sayacı ve boyut işleçleri
Konum sayacı, assembler'ın bir sonraki baytın bağıl konumunu tuttuğu sayaçtır ve kaynakta $ ile okunur. $ kullanıldığı satırdaki değerini verir. Bu, bir dizinin boyutunu elle saymadan bulmanın yoludur.
s DB 'Merhaba', 0
n EQU $ - s ; 8
w DW 10 DUP(0)s'nin tanımı bittiğinde konum sayacı dizginin son baytından bir sonraki adresi gösterir. $ - s iki adresin farkıdır, yani dizginin bayt sayısı: 7 harf + 1 sonlandırıcı = 8. Bu fark bir EQU sabitine atanır ve derleme anı sabitine dönüşür. Dizgi değiştirilirse n kendiliğinden yeni uzunluğu alır. EQU satırının dizinin hemen ardından gelmesi şarttır. Araya başka bir tanım girerse onun baytları da sayılır ve n yanlış olur.
MASM aynı bilgiyi değişkenin tanımından okuyan işleçler de sunar. TYPE bir elemanın bayt boyunu, LENGTHOF eleman sayısını, SIZEOF ise toplam bayt sayısını verir (eski MASM sürümlerinde ve emu8086'da LENGTH ve SIZE).
| İfade | Değer | Anlamı |
|---|---|---|
n | 8 | s'nin bayt sayısı |
TYPE s | 1 | eleman boyu (DB) |
LENGTHOF s | 8 | eleman sayısı |
TYPE w | 2 | eleman boyu (DW) |
LENGTHOF w | 10 | eleman sayısı |
SIZEOF w | 20 | LENGTHOF × TYPE |
Bu ifadelerin hepsi derleme anı sabitidir. MOV CX, LENGTHOF w komuta 10 sayısını gömer ve B9 0A 00 olarak kodlanır; bellekte ayrıca yer kaplamaz. Sözcük dizisinde döngü sayacı için LENGTHOF, bayt uzunluğu için SIZEOF kullanılır. Bayt dizisinde ikisi aynıdır.
4.2.5.4Kod izleme: dizi boyutuyla en büyük eleman
ORG 100h
JMP basla
dizi DB 12, 47, 5, 31
n EQU $ - dizi ; 4
enb DB ?
basla:
MOV CX, n ; CX = 4
MOV SI, 0
MOV AL, dizi[SI] ; ilk aday
bak:
CMP AL, dizi[SI]
JAE gec ; AL >= eleman
MOV AL, dizi[SI] ; yeni aday
gec:
INC SI
LOOP bak
MOV enb, AL
RETBu programda veriler başta durur. 0100h'deki iki baytlık JMP basla (EB 05) veriyi atlar. dizi 0102h–0105h'yi, enb 0106h'yi kaplar; kod 0107h'den devam eder. n EQU $ - dizi satırı dizinin hemen ardındadır: o anda $ = 0106h, fark 0106h − 0102h = 4'tür. MOV CX, n bu yüzden B9 04 00 olarak kodlanır.
Algoritma “şimdiye kadarki en büyük” fikrine dayanır. AL ilk elemanla başlar ve aday olur. Her turda aday sıradaki elemanla karşılaştırılır. Aday küçükse eleman yeni aday olur. CMP AL, dizi[SI] AL − eleman işlemini yapar ve yalnız bayrakları kurar. JAE (jump if above or equal) işaretsiz karşılaştırır: CF = 0 ise, yani AL ≥ eleman ise atlar. LOOP bak CX'i bir azaltır ve CX sıfır değilse bak'a döner. LOOP ve INC CF'yi değiştirmez; tablodaki CF son CMP'nin sonucudur.
| Tur | Karşılaştırma | CF | JAE | AL | SI | CX |
|---|---|---|---|---|---|---|
| başlangıç | — | 0 | — | 0Ch (12) | 0000h | 0004h |
| 1 | 12 − 12 | 0 | atlar | 0Ch (12) | 0001h | 0003h |
| 2 | 12 − 47 | 1 | atlamaz | 2Fh (47) | 0002h | 0002h |
| 3 | 47 − 5 | 0 | atlar | 2Fh (47) | 0003h | 0001h |
| 4 | 47 − 31 | 0 | atlar | 2Fh (47) | 0004h | 0000h |
JAE atlamaz ve AL = 47 (2Fh) olur. Sağda döngü sonrası MOV enb, AL: en büyük eleman belleğe yazılır.Dördüncü turdan sonra CX = 0 olur ve LOOP düşer. MOV enb, AL sonucu 0106h'ye yazar: enb = 2Fh = 47. Döngü sayısı hiçbir yerde elle yazılmadı. Diziye bir eleman eklense n 5 olur ve kod değişmeden beş tur döner. Karşılaştırma işaretsizdir, çünkü dizideki değerler 0–255 aralığında düşünülmüştür. Dizi negatif sayılar içerseydi JAE yerine işaretli JGE kullanılırdı; aksi hâlde −1 (FFh) en büyük sayı olarak seçilirdi.
4.2.6Örnek programlar
Değişkenlerle ilgili bütün kurallar, birkaç satırlık küçük programlarda bir araya gelir. Bu kesimdeki programlar emu8086'nın örnek programlarına dayanır. Her biri değişkenler arasında veri taşıma, aritmetik, karşılaştırma ya da çok sözcüklü değer gibi temel bir işi yapar. Her programdan sonra bir izleme tablosu yazmaçları, bayrakları ve değişkenleri her adımda verir.
4.2.6.1Değişkenler arasında veri taşıma
jmp start
value1 DW 10 ; 2 baytlık değişken
value2 DW 0 ; ikinci değişken
start:
MOV AX, value1 ; AX ← value1
MOV value2, AX ; value2 ← AX
end startProgram veriyi başa koyan düzeni kullanır. 0100h'deki jmp start iki baytlık kısa bir atlamadır (EB 04): 04 sayısı, atlamadan sonraki komutun adresine (0102h) eklenecek uzaklıktır. Hedef 0102h + 4 = 0106h olur. Aradaki dört bayt değişkenlerdir: value1 0102h'de (0A 00), value2 0104h'te (00 00). jmp işlemciyi verilerin üzerinden atlatır. Atlama olmasaydı 0A 00 baytları OR AL, [BX+SI] komutu olarak yürütülürdü.
8086'da bellekten belleğe MOV olmadığı için taşıma iki adımda yapılır. MOV AX, value1 (A1 02 01) değeri AX'e alır: AX = 000Ah. MOV value2, AX (A3 04 01) AX'i belleğe yazar: 0104h'te artık 0A 00 vardır. Her iki komut da AX'e özel kısa biçimdir ve 10 saat turu sürer. Ara yazmaç olarak AX seçilmesinin nedeni de budur. Son satırdaki end start kaynağın sonunu bildirir ve programın giriş noktasının start etiketi olduğunu söyler.
| Satır | IP | AX | value1 (0102h) | value2 (0104h) |
|---|---|---|---|---|
1 jmp start | 0106h | 0000h | 000Ah (0A 00) | 0000h (00 00) |
5 MOV AX, value1 | 0109h | 000Ah | 000Ah | 0000h |
6 MOV value2, AX | 010Ch | 000Ah | 000Ah | 000Ah (0A 00) |
MOV AX, value1: AX = 000Ah. Sağda MOV value2, AX: value2'nin baytları 0A 00 olur; artık value1 = value2 = 10.Bu program emu8086 örneğinin kısaltılmış biçimidir ve bir sonlandırma komutu içermez. MOV value2, AX'ten sonra işlemci 010Ch'deki baytları yürütmeye devam eder. Bağımsız çalışacak bir programda bu satırdan sonra RET yazılır.
4.2.6.2Değişkenleri toplama
jmp start
value1 DW 105h ; ilk değer 105h
value2 DW 103h ; ilk değer 103h
result DW ? ; belirsiz
start:
MOV AX, value1 ; AX ← value1
ADD AX, value2 ; AX ← AX + value2
MOV result, AX ; result ← AX
end startÜç değişken 0102h, 0104h ve 0106h'dedir; jmp start bu kez altı bayt atlar (EB 06). result ? ile tanımlanmıştır. emu8086 bu baytlara 00 00 koyar ama değerine güvenilmez; program onu okumadan önce yazar.
MOV AX, value1 AX = 0105h yapar. ADD AX, value2 (03 06 04 01) ise MOV'dan farklı olarak bellek işlenenini doğrudan kullanabilir: AX ile [0104h]'teki sözcüğü toplar. BIU sözcüğü okur, ALU toplar ve sonuç AX'e yazılır. Komut 9 + 6 = 15 saat turu sürer. Toplam basamak basamak yapılabilir: 5 + 3 = 8, 0 + 0 = 0, 1 + 1 = 2; hiçbir basamakta elde yoktur ve AX = 0208h olur. Onluk sağlama: 261 + 259 = 520 = 208h.
ADD altı durum bayrağını sonuca göre kurar. En üst bitten elde çıkmadığı için CF = 0'dır. Sonuç sıfır değildir, ZF = 0. En üst bit 0'dır, SF = 0. İki pozitif sayının toplamı pozitif kaldığı için işaretli taşma yoktur, OF = 0. Düşük nibble'da 5 + 3 = 8 olur ve bit 3'ten bit 4'e elde geçmez, AF = 0. Eşlik bayrağı yalnız sonucun düşük baytına bakar: 08h = 0000 1000'de tek sayıda (bir) 1 vardır, bu yüzden PF = 0'dır.
| Satır | AX | CF | ZF | SF | OF | PF | AF | result (0106h) |
|---|---|---|---|---|---|---|---|---|
| başlangıç | 0000h | 0 | 0 | 0 | 0 | 0 | 0 | 00 00 |
6 MOV AX, value1 | 0105h | 0 | 0 | 0 | 0 | 0 | 0 | 00 00 |
7 ADD AX, value2 | 0208h | 0 | 0 | 0 | 0 | 0 | 0 | 00 00 |
8 MOV result, AX | 0208h | 0 | 0 | 0 | 0 | 0 | 0 | 08 02 |
ADD AX, value2: 0105h + 0103h = 0208h, bayrakların hepsi 0. Sağda MOV result, AX: bellekte little-endian 08 02.Toplam bir sözcüğe sığmasaydı CF = 1 olurdu. Örneğin value1 = 0F000h, value2 = 2000h için 0F000h + 2000h = 11000h'dir; AX'e yalnız 1000h sığar ve dışarı taşan 1 CF'ye gider. Taşmayı denetleyen bir program ADD'den hemen sonra JNC ile CF'ye bakar:
MOV AX, v1
ADD AX, v2
MOV v3, AX
JNC son
MOV tasma, 1
son:MOV v3, AX bayrakları değiştirmediği için JNC hâlâ toplamanın CF'sini görür. 105h + 103h için CF = 0'dır ve tasma 0 kalır; 0F000h + 2000h için v3 = 1000h, tasma = 1 olur.
4.2.6.3İki değişkeni karşılaştırma
ORG 100h
MOV AX, value1 ; AX ← value1
CMP AX, value2 ; AX − value2
JGE greater_or_equal
MOV AX, value2 ; küçükse value2
JMP store_max
greater_or_equal:
MOV AX, value1
store_max:
MOV max_value, AX ; sonucu sakla
RET
value1 DW 10
value2 DW 5
max_value DW ?Program iki sayıdan büyük olanı max_value'ya yazar. Kod 0100h–0114h arasındadır; value1 0115h'de, value2 0117h'de, max_value 0119h'dedir. Bu düzende veriler RET'ten sonra gelir.
CMP (compare) çıkarma yapar ama sonucu saklamaz; yalnız bayrakları kurar. CMP AX, value2 000Ah − 0005h = 0005h hesaplar: CF = 0, ZF = 0, SF = 0, OF = 0. AX değişmez. JGE (jump if greater or equal) işaretli karşılaştırmanın sonucuna bakar ve SF = OF ise atlar. Burada SF = OF = 0 olduğu için atlar ve greater_or_equal'a gider. JGE'nin makine kodu 7D 05'tir: hedef, sonraki komutun adresi 0109h'ye 5 eklenerek bulunur, 010Eh.
Atlanılan yerdeki MOV AX, value1 AX'i yeniden 000Ah yapar. AX zaten value1'i tuttuğu için bu satır gereksizdir ama zarar vermez. MOV max_value, AX sonucu 0119h'ye yazar: 0A 00. RET denetimi DOS'a geri verir.
| Satır | AX | CF | ZF | SF | OF | max_value | Açıklama |
|---|---|---|---|---|---|---|---|
2 MOV AX, value1 | 000Ah | 0 | 0 | 0 | 0 | 0000h | |
3 CMP AX, value2 | 000Ah | 0 | 0 | 0 | 0 | 0000h | 10 − 5 = 5, sonuç atılır |
4 JGE ... | 000Ah | 0 | 0 | 0 | 0 | 0000h | SF = OF: atlar |
8 MOV AX, value1 | 000Ah | 0 | 0 | 0 | 0 | 0000h | gereksiz satır |
10 MOV max_value, AX | 000Ah | 0 | 0 | 0 | 0 | 000Ah | bellekte 0A 00 |
JGE: SF = OF olduğu için atlanır. Sağda MOV max_value, AX: büyük olan 10 (000Ah) belleğe yazılır.4.2.6.4Karşılaştırma: diğer yol
Programın öteki dalını görmek için değişkenlerin değerleri değiştirilir:
value1 DW 3
value2 DW 7
max_value DW ?Kodun geri kalanı aynıdır; adresler de değişmez. MOV AX, value1 AX = 0003h yapar. CMP AX, value2 0003h − 0007h hesaplar. İşaretsiz bakışla 3'ten 7 çıkmaz ve ödünç gerekir: CF = 1. 16 bitlik sonuç FFFCh'dir, yani −4; en üst bit 1 olduğu için SF = 1 olur. İşaretli taşma yoktur, çünkü 3 − 7 = −4 aralığın içindedir: OF = 0. Düşük nibble'da 3 − 7 de ödünç ister, AF = 1. Sonucun düşük baytı FCh = 1111 1100'de altı tane 1 vardır, PF = 1.
Bu kez SF = 1, OF = 0'dır ve SF ≠ OF olduğu için JGE atlamaz. İşlemci sıradaki komuta geçer: MOV AX, value2 AX = 0007h yapar. JMP store_max koşulsuz atlar ve greater_or_equal dalını geçer. MOV max_value, AX 0007h'yi yazar. Program iki daldan hangisinden geçerse geçsin store_max'ta birleşir.
| Satır | AX | CF | ZF | SF | OF | max_value | Açıklama |
|---|---|---|---|---|---|---|---|
2 MOV AX, value1 | 0003h | 0 | 0 | 0 | 0 | 0000h | |
3 CMP AX, value2 | 0003h | 1 | 0 | 1 | 0 | 0000h | 3 − 7 = FFFCh, atılır |
4 JGE ... | 0003h | 1 | 0 | 1 | 0 | 0000h | SF ≠ OF: atlamaz |
5 MOV AX, value2 | 0007h | 1 | 0 | 1 | 0 | 0000h | büyük olan |
6 JMP store_max | 0007h | 1 | 0 | 1 | 0 | 0000h | koşulsuz atlama |
10 MOV max_value, AX | 0007h | 1 | 0 | 1 | 0 | 0007h | bellekte 07 00 |
CMP AX, value2: 3 − 7 için CF = 1 ve SF = 1. Sağda JGE atlamadığı için MOV AX, value2 yürütülür: AX = 0007h.4.2.6.5İşaretli ve işaretsiz karşılaştırma
Karşılaştırma programındaki atlama komutu bilinçli olarak JGE seçilmiştir. 8086'da iki atlama ailesi vardır. JGE, JG, JL ve JLE işaretli karşılaştırma yapar ve SF, OF ve ZF'ye bakar. JAE, JA, JB ve JBE işaretsiz karşılaştırma yapar ve CF ile ZF'ye bakar. Aynı CMP iki ailenin de ihtiyaç duyduğu bayrakları birlikte kurar; programcı yorumu atlama komutuyla seçer.
value1 = −1 (bellekte FFFFh) ve value2 = 5 olsun. CMP AX, value2 FFFFh − 0005h = FFFAh hesaplar: CF = 0 (işaretsiz olarak 65 535 ≥ 5), SF = 1, OF = 0. JGE SF ≠ OF olduğu için atlamaz ve max_value = 0005h olur; bu işaretli bakışla doğrudur, çünkü −1 < 5. JAE yazılsaydı CF = 0 olduğu için atlardı ve max_value = FFFFh olurdu; bu da işaretsiz bakışla doğrudur, çünkü 65 535 > 5.
Şekil 4.2.25 aynı desenin iki sayı doğrusundaki yerini gösterir. Hangi atlamanın doğru olduğu, değişkenin neyi temsil ettiğine bağlıdır. Sıcaklık ya da konum gibi negatif olabilen bir değer işaretli, bir sayaç ya da bir adres işaretsiz atlama ister.
Programın sonundaki RET zorunludur. Veri tanımları kodun hemen ardından gelir. RET olmasaydı işlemci 0115h'deki 0A 00 05 00 … baytlarını komut olarak yürütürdü. Çözüm ya kodun sonuna RET koymak ya da veriyi başa alıp jmp start ile atlamaktır. Programın bir iyileştirmesi de mümkündür: greater_or_equal altındaki MOV AX, value1 gereksizdir. JGE doğrudan store_max'a atlayabilir ve program bir komut kısalır.
4.2.6.6Çift sözcük değişken
8086 16 bitlik bir işlemcidir; 32 bitlik bir yazmacı yoktur. Yine de 65 535'ten büyük sayılarla çalışmak gerekebilir. Bu tür değerler bellekte DD ile 4 bayt olarak saklanır. İşlemcide ise iki 16 bitlik yazmaçta, geleneksel olarak DX:AX çiftinde tutulur: DX yüksek sözcüğü, AX düşük sözcüğü alır.
jmp start
mydouble dd 12345678h ; double word
mywords dw 5678h ; 2 ayrı word
dw 1234h
mybytes db 78h ; 4 ayrı byte
db 56h
db 34h
db 12h
data dd 00010010001101000101011001111000b
start: ; data'yı DX:AX'e yükle
mov ax, word ptr data
mov dx, word ptr [data+2]Programdaki dört tanımın bellekteki karşılığı birebir aynıdır. mydouble değeri tek bir çift sözcük olarak verir; assembler onu little-endian sırayla 78 56 34 12 diye yazar. mywords aynı baytları iki sözcükle üretir: önce düşük sözcük 5678h (78 56), sonra yüksek sözcük 1234h (34 12). mybytes dört baytı tek tek yazar. data değeri ikili tabanda verir: dörder bitlik gruplar 0001 0010 0011 0100 0101 0110 0111 1000, yani 12345678h'dir. Assembler için sayının hangi tabanda ya da hangi parçalarla yazıldığı önemsizdir; bellekte kalan, little-endian dizilmiş dört bayttır.
4.2.6.7Kod izleme: DX:AX'e 32 bitlik yükleme
mov ax, word ptr data ; data+0
mov dx, word ptr [data+2] ; data+2
ret
data dd 12345678hdata DD türündedir; bir sözcük yazmacıyla kullanılınca boyutlar eşleşmez. word ptr assembler'a bu erişimde yalnız iki bayt okunacağını söyler. Kod 8 bayttır; data 0108h'dedir. mov ax, word ptr data (A1 08 01) 0108h ve 0109h'deki 78 56 baytlarını okur: AX = 5678h. mov dx, word ptr [data+2] (8B 16 0A 01) 010Ah ve 010Bh'deki 34 12 baytlarını okur: DX = 1234h. DX:AX çifti artık 1234:5678h, yani 12345678h değerini tutar.
| Satır | Okunan adresler | DX | AX |
|---|---|---|---|
1 mov ax, word ptr data | 0108h, 0109h (78 56) | 0000h | 5678h |
2 mov dx, word ptr [data+2] | 010Ah, 010Bh (34 12) | 1234h | 5678h |
32 bitlik bir değer üzerinde aritmetik de iki parçada yapılır. data dd 0FFFFFFFFh değerine 1 eklemek için önce düşük sözcüğe 1 eklenir, sonra düşük sözcükten taşan elde yüksek sözcüğe katılır:
ADD AX, 1 ; düşük sözcük
ADC DX, 0 ; yüksek sözcük + eldeBaşta AX = FFFFh, DX = FFFFh'dir. ADD AX, 1 AX = 0000h ve CF = 1 yapar. ADC (add with carry) ikinci işlenene ek olarak CF'yi de toplar: DX = FFFFh + 0 + 1 = 0000h. Sonuç DX:AX = 00000000h'dir ve bu son toplamadan da CF = 1 çıkar, çünkü 32 bitlik sonuç da taşmıştır. Aynı işlem 0001FFFFh için yapılırsa DX:AX = 00020000h olur. İkinci satırda ADC yerine ADD DX, 0 yazılsaydı elde kaybolur ve sonuç yanlış olarak 00010000h çıkardı.
4.2.6.8Değişkenlerle ilgili sık hatalar
Değişkenlerle çalışırken yapılan hataların çoğu assembler'ın yakalayamadığı türdendir: program derlenir, çalışır ve yanlış sonuç verir. Aşağıdaki liste en sık karşılaşılanları toplar.
- Değer ile adresi karıştırmak.
MOV SI, diziilk sözcüğü yükler; adres içinLEA SI, diziya daMOV SI, OFFSET dizigerekir. - Bellekten belleğe taşımak.
MOV v1, v2yoktur; değer bir yazmaçtan geçer. Assembler bunu yakalar. - Sözcük dizisinde indis. 3. eleman
w[4]'tedir; indis bayt cinsindendir.w[1]iki elemanın yarılarını birleştirir. ?değerine güvenmek.toplam DW ?sıfır olmayabilir; toplama başlamadanMOV toplam, 0yazılır.- Soneki unutmak.
DW 10onluk 10'dur (000Ah); onaltılık için10hyazılır. Harfle başlayan onaltılık sayılar başa 0 alır:0FFh,0ABCDh. - Ad çakışması.
SUTUN EQU 4ilesutun DB 1aynı addır; assembler “iki kez tanımlı” hatası verir. - Veriyi kodun yoluna koymak.
RETya daJMPyoksa veri baytları komut sanılır. - Sonlandırıcıyı unutmak. 0 ya da '$' yoksa yazdırma dizginin arkasındaki baytlarla sürer.
4.2.6.9Veri tanımlama özeti
| Yazım | Anlamı | Bellek |
|---|---|---|
x DB 7 | bir bayt | 07 |
y DW 1234h | bir sözcük | 34 12 |
z DD 12345678h | çift sözcük | 78 56 34 12 |
s DB 'Hi', 0 | dizgi + sonlandırıcı | 48 69 00 |
t DB 3 DUP(0) | tekrar | 00 00 00 |
u DW ? | ilk değersiz | ?? ?? (emu8086: 00 00) |
k EQU 10 | sabit | yer kaplamaz |
Adres almak için LEA BX, x ya da MOV BX, OFFSET x, değeri okumak için MOV AL, x yazılır. Bir erişimin boyutu değişkenin türünden farklı olacaksa byte ptr ya da word ptr kullanılır.
4.2.7Özet
- Değişken, bellekte bir ada bağlanmış veri alanıdır. Assembler adı bir bağıl konumla değiştirir; çalışan programda ad yoktur, yalnız komutun içine gömülü adres vardır.
DB,DWveDDsırasıyla 1, 2 ve 4 bayt ayırır ve değişkene BYTE, WORD ya da DWORD türü verir.?ilk değersiz alan ayırır; değerine güvenilmez.- Bellekte yalnız bit deseni saklanır. Desenin işaretli mi işaretsiz mi olduğunu onu kullanan komut belirler: CF,
JAE,JBişaretsiz; OF,JGE,JL,CBWişaretli yorum yapar. - Assembler tür denetimi yapar: yazmaç ile değişkenin boyutu eşleşmelidir. Türsüz işlenenlerde ve bilinçli boyut değişikliğinde
BYTE PTR/WORD PTRkullanılır.LABELaynı adrese ikinci bir ad ve tür verir. - 8086 little-endian'dır: çok baytlı değerlerin düşük baytı düşük adrestedir. Bu kural komutların içindeki adreslere de uygulanır.
- Assembler konum sayacıyla her baytın bağıl konumunu hesaplar ve iki geçişte adları adreslere çevirir.
ORG 100h.COM programının PSP'den sonra, 0100h'den başladığını bildirir; .COM'da CS = DS = ES = SS'dir. - Tek adreste başlayan bir sözcük iki yol döngüsünde okunur ve 4 saat turu kaybettirir.
EVENsözcükleri çift adrese hizalar. - Veriler ya
RET'ten sonra ya da birJMP'in arkasına yazılır; işlemci yolunun üzerindeki veri baytlarını komut sanır. .EXE programında DS önce yüklenir. - Dizi, ardışık aynı türden öğelerdir. İndis bayt cinsindendir: sözcük dizisinde i. eleman
2 × iuzaklıktadır.DUPdeğer listelerini tekrarlar ve iç içe kullanılabilir. - Dizgiler
DBile tanımlanır ve sonları 0, '$' ya da bir uzunluk baytıyla belirlenir. Büyük ve küçük harf yalnız bit 5'te ayrılır; rakam karakterinden '0' çıkarılarak sayı elde edilir. - İki boyutlu diziler satır öncelikli düzende saklanır: uzaklık = (satır × sütun sayısı + sütun) × eleman boyu.
LEAetkin adresi çalışma anında hesaplar ve belleğe erişmez;OFFSETderleme anında sabit bir adres verir. İndisli adres yalnızLEAile alınır. İşaretçi değişkenler adresi bellekte saklar.EQUsabitleri yalnız derleme anında vardır, komuta anlık değer olarak gömülür ve bellekte yer kaplamaz.$ - dizibir dizinin boyutunu kendiliğinden hesaplar.- Bellekten belleğe taşıma yoktur; değer bir yazmaçtan geçer.
CMPsonucu saklamadan bayrakları kurar. 32 bitlik değerler DX:AX çiftinde tutulur veADD/ADCçiftiyle toplanır.
4.2.8Kavram listesi
- değişken variable
- veri tanımlama (DB / DW / DD) data definition
- ilk değer initial value
- direktif directive
- bayt byte
- sözcük word
- çift sözcük double word
- işaretli / işaretsiz sayı signed / unsigned number
- ikiye tümleyen two's complement
- işaret genişletme sign extension
- veri türü belirtici byte ptr / word ptr
- little-endian little-endian
- bağıl konum offset
- konum sayacı ($) location counter
- .COM programı .COM program
- PSP (program kesimi öneki) program segment prefix
- hizalı / hizasız erişim aligned / unaligned access
- yol döngüsü bus cycle
- dizi array
- dizgi string
- ASCII ASCII
- satır öncelikli düzen row-major order
- etkin adres (EA) effective address
- OFFSET işleci OFFSET operator
- işaretçi değişken pointer variable
- sabit (EQU) constant
- anlık değer immediate value
- elde bayrağı (CF) carry flag
- taşma bayrağı (OF) overflow flag
4.2.9Kendini Dene
p DB 'Az', 0·q DW 300·r DD 0Ahtanımları 0300h'den başlıyor. Her baytın adresi ve değeri nedir? Tanımlar toplam kaç bayt kaplar?Cevap
0300h: 41h ('A'), 0301h: 7Ah ('z'), 0302h: 00h. 300 = 012Ch olduğu için 0303h: 2Ch, 0304h: 01h.
r0305h–0308h: 0A 00 00 00. Toplam 3 + 2 + 4 = 9 bayt.tab DW 5 DUP(4 DUP(?))kaç bayt kaplar? Satır öncelikli düzende [3][2] elemanınıntab'a göre bayt uzaklığı nedir?Cevap
5 × 4 = 20 sözcük = 40 bayt. Uzaklık (3 × 4 + 2) × 2 = 28 = 1Ch. Okumak için
MOV SI, 28·MOV AX, tab[SI]yazılabilir.s DB 'Ders', 13, 10, '$'satırının hemen ardındann EQU $ - syazılıyor.nkaçtır? Arayat DB 0girseydi ne olurdu?Cevap
4 harf + CR + LF + '$' = 7. Araya
t DB 0girseydi$bir bayt ilerlemiş olur ven= 8 çıkardı; sabit artık dizginin boyutunu vermezdi.BX = 0100h, DI = 0020h ve CF = 1 iken
LEA SI, [BX+DI+4]yürütülüyor. SI ne olur? CF değişir mi? Bellekten okuma yapılır mı?Cevap
SI = 0100h + 0020h + 4 = 0124h.
LEAbayrakları değiştirmez, CF = 1 kalır. Yalnız adres hesaplanır; hiçbir okuma yol döngüsü yapılmaz.AL = 80h, BL = 7Fh iken
CMP AL, BLyürütülüyor. CF, SF ve OF ne olur? ArdındanJAatlar mı,JGatlar mı?Cevap
80h − 7Fh = 01h. İşaretsiz olarak 128 ≥ 127, ödünç yok: CF = 0, ZF = 0,
JAatlar. İşaretli olarak −128 − 127 = −255 aralık dışıdır: OF = 1, sonuç 01h olduğu için SF = 0. SF ≠ OF olduğundan −128 < 127 sonucu çıkar veJGatlamaz.w DW 0102h, 0304htanımlı.MOV AX, w[2]veMOV BL, BYTE PTR w+1sonrası AX ve BL nedir?MOV BL, w+1neden derlenmez?Cevap
w[2]ikinci elemandır: AX = 0304h. Bellek 02 01 04 03 olduğu içinw+1'deki bayt 01h'dir: BL = 01h.wWORD türünde olduğu içinMOV BL, w+1boyut uyuşmazlığı verir;BYTE PTRgerekir.MOV DX, OFFSET msgileLEA DX, msgaynı sonucu verir. Hangisi kaç bayttır ve 8086'da yaklaşık kaç saat turu sürer? Hangisi indisli adres için kullanılabilir?Cevap
MOV DX, OFFSET msganlık değer yüklemesidir: 3 bayt (BA + 16 bit), 4 tur. emu8086LEA DX, msgsatırını aynı koda çevirir; ikisi de 3 bayt ve 4 turdur (MASM'daki gerçek LEA ise 4 bayt, 8D 16 + 16 bit, 2 + 6 = 8 turdur). İndisli adres (LEA DX, msg[SI]) yalnız LEA ile alınır.DX:AX = 0001FFFEh iken 3 eklemek için
ADD AX, 3·ADC DX, 0yürütülüyor. Her komuttan sonra AX, DX ve CF nedir?Cevap
ADD AX, 3: FFFEh + 3 = 10001h; AX = 0001h, CF = 1.ADC DX, 0: 0001h + 0 + 1 = 0002h, CF = 0. Sonuç DX:AX = 00020001h = 0001FFFEh + 3.Konum sayacı 0105h iken
a DB 1, 2·EVEN·w DW 5yazılıyor.whangi adrese yerleşir?EVENolmasaydıMOV AX, wneden daha yavaş olurdu?Cevap
a0105h–0106h'yi kaplar, sayaç 0107h olur.EVEN0107h'ye bir dolgu baytı koyar vew0108h'ye yerleşir.EVENolmasaydıw0107h'de, tek adreste başlardı. Sözcük iki yol döngüsünde okunur ve erişim 4 tur uzardı (10 yerine 14).
4.2.10İleri okuma
- emu8086 belgeleri, 8086 assembler tutorial for beginners, 3. bölüm (variables): DB, DW, diziler, DUP, LEA, OFFSET ve EQU, bu notun programlarının çıkış noktası olan örneklerle anlatılır.
- Kip R. Irvine, Assembly Language for x86 Processors, veri tanımlama bölümü: veri direktifleri, little-endian düzen,
$, TYPE, LENGTHOF ve SIZEOF işleçleri ayrıntılı örneklerle verilir. - Barry B. Brey, The Intel Microprocessors, adresleme modları ve veri taşıma komutları bölümleri: LEA, LDS/LES ve doğrudan adreslemenin makine kodu ve donanım düzeyindeki işleyişi ele alınır.
- Microsoft, MASM 6.1 Programmer's Guide: DB/DW/DD, DUP, EQU,
=, OFFSET, LABEL ve EVEN direktiflerinin kesin tanımları ve kuralları.
4.2.11Kaynaklar
- emu8086 belgeleri, 8086 assembler tutorial for beginners, bölüm 3: variables (DB, DW, DUP, LEA, OFFSET, EQU).
- Barry B. Brey, The Intel Microprocessors, 8. baskı, Pearson.
- Kip R. Irvine, Assembly Language for x86 Processors, Pearson (veri tanımlama direktifleri).
- Microsoft, MASM 6.1 Programmer's Guide: DB/DW/DD, DUP, EQU, OFFSET.
- Intel, The 8086 Family User's Manual, 1979 (komut kodlaması ve saat turu sayıları).
İlgili sayfalar: Bölüm 4.2 sunumu · Çalışma soruları · Terim sözlüğü · © Sercan Külcü. Tüm hakları saklıdır.