Mikroişlemciler · Ders Notu
Bölüm 9.1: Makrolar
Assembly programlarında aynı birkaç komut sık sık yan yana yazılır: bir karakteri ekrana basmak, birkaç yazmacı yığına saklamak, iki sayıyı toplayıp belleğe yazmak. Prosedür bu tekrarı çalışma anında, CALL ve RET ile çözer. Makro ise aynı işi derleme anında yapar: assembler makronun adını gördüğü her yere gövdesini yeniden yazar. İşlemci makroyu hiç görmez; yalnız ortaya çıkan komutları yürütür. Not önce makronun nasıl tanımlandığını ve açıldığını, parametrelerin neden metin gibi davrandığını anlatır. Ardından makro ile prosedür arasındaki boyut ve hız dengesini ve LOCAL direktifinin çözdüğü sorunu ele alır. PUTC ve POWER makroları üzerinden yazmaç koruma ve sınır durumları işlenir. Son kesimler tekrar bloklarını ve koşullu derlemeyi, yani assembler'ın kaynak üzerinde yaptığı daha ileri metin işlemlerini ele alır. Her örnek, açılmış kodun adım adım izlemesiyle yazmaç ve bellek düzeyinde gösterilir.
Öğrenme çıktıları
- Makro tanımı
- MACRO ve ENDM ile parametreli makro yazmak; makronun kullanıldığı satırdan önce tanımlanması gerektiğini açıklamak.
- Genişletme
- Bir makro çağrısının hangi komutlara açılacağını, parametreleri metin olarak yerine koyarak elle bulmak.
- Makro mu, prosedür mü?
- Kod boyutunu bayt bayt, ek süreyi saat turu olarak hesaplayıp verilen bir iş için makroyu ya da prosedürü gerekçesiyle seçmek.
- LOCAL
- Etiket içeren makrolarda Duplicate declaration hatasının nedenini açıklamak ve LOCAL ile her açılıma benzersiz etiket vermek.
- Yan etkiler ve sınır durumları
- Makronun bozduğu yazmaçları PUSH ve POP ile korumak; parametre tuzaklarını, işlem önceliği hatalarını ve LOOP'un CX = 0 tuzağını fark etmek.
- İleri makro özellikleri
- REPT, IRP ve IRPC ile derleme anında kod ve tablo üretmek; IF, IFB ve IFIDN ile koşullu derleme yapmak.
9.1.1Makro kavramı
9.1.1.1Assembler'ın yaptığı metin işi
Bir assembly programı iki ayrı aşamadan geçer. Önce assembler (assembler) kaynak dosyayı okur ve makine koduna çevirir. Sonra işlemci bu makine kodunu bellekten getirip yürütür. Kaynakta yazılan her şey birinci aşamada bir karşılık bulur, ama her satır makine koduna dönüşmez. ORG 100h, DB, EQU gibi direktifler (directive) assembler'a bilgi verir; bunlardan bazıları bayt üretir, bazıları hiç üretmez. Makro da bu birinci aşamanın aracıdır.
Makro, tekrar eden bir kod parçasına ad verir. Bu yönüyle prosedüre benzer. Fark, adın nerede işlendiğindedir. Prosedürün adı makine kodunda bir adrese dönüşür; işlemci çalışma anında CALL ile o adrese gider ve RET ile geri döner. Makronun adı ise makine koduna hiç girmez. Assembler, makronun kullanıldığı her satırı silip yerine makronun gövdesini yazar. Bu işleme makro genişletme ya da açılım (macro expansion) denir. Genişletme bittikten sonra kaynakta artık makro yoktur; yalnız sıradan komutlar vardır.
Makro (macro), MACRO ve ENDM direktifleri arasına yazılan, bir adı ve isteğe bağlı parametreleri olan kod kalıbıdır. Assembler, makronun adını bir komut gibi kullanan her satırı, gövdenin parametreleri yerine konmuş bir kopyasıyla değiştirir. Bu değiştirmeye makro genişletme (açılım) denir ve derleme anında, program çalışmadan önce yapılır.
Şekil 9.1.1 bu süreci özetler. Kaynakta tek satır olarak duran PUTC 'A', assembler'dan geçtikten sonra beş komuta dönüşür. Makine kodunda bir çağrı, bir dönüş adresi ya da bir atlama yoktur. İşlemci açısından bu beş komut, programcının elle yazdığı beş komuttan farksızdır.
PUTC 'A' satırının yerine assembler gövdeyi yazar; makine kodunda çağrı yoktur.Bu bakış açısından üç önemli sonuç çıkar. Birincisi, hiç kullanılmayan bir makro makine koduna tek bayt eklemez. Assembler tanımı okur, adını ve gövdesini bir tabloya yazar, ama gövdeden kod üretmez. Kod ancak makro kullanıldığında, kullanıldığı yerde üretilir. Bu yüzden yüzlerce makro içeren bir kütüphane dosyasını programa eklemek, programı büyütmez.
İkincisi, makro kullanıldığı satırdan önce tanımlanmış olmalıdır. Assembler kaynak dosyayı yukarıdan aşağıya okur. PUTC 'A' satırına geldiğinde PUTC'yi henüz tanımıyorsa bu satırı bilinmeyen bir komut sayar ve hata verir. Prosedürde böyle bir sıra kısıtı yoktur. Assembler iki geçişli çalıştığı için bir prosedürün adresini, prosedür dosyanın sonunda yazılmış olsa da ikinci geçişte bulur. Makro ise bir adres değil, bir metindir. Metni bilmeden yerine koyamaz.
Üçüncüsü, makronun her kullanımı programı büyütür. Aynı makro on yerde kullanılırsa gövde on kez yazılır. Prosedürün gövdesi ise bellekte bir kez durur. Bu fark, makro ile prosedür arasındaki seçimin temelidir.
9.1.1.2Makro tanımlama
Bir makronun tanımı üç parçadan oluşur: başlık satırı, gövde ve bitiş satırı. Genel biçim şöyledir:
ad MACRO [parametre1, parametre2, ...]
<komutlar>
ENDMad makronun adıdır. Makro kullanılırken bu ad, bir komutun yerine yazılır: MOV yazılan yere PUTC yazılır. Adın ardından virgülle ayrılmış parametre adları gelir. Bunlar biçimsel adlardır; tanımda yalnız birer yer tutucudur. Çağrıda verilen değerler bu adların yerine metin olarak konur. Parametre hiç olmayabilir: yalnız bir kalıbı adlandıran makrolar da vardır.
Gövde, makro her kullanıldığında yeniden yazılacak satırlardır. Komut, direktif, başka bir makronun çağrısı, hatta veri tanımı olabilir. ENDM (end macro) gövdeyi bitirir. Prosedürdeki ad ENDP kalıbından farklı olarak ENDM'in önüne makronun adı yazılmaz.
Parametre adları gövdede yerine konacak metni işaretler. Assembler, gövdedeki her sözcüğü parametre adlarıyla karşılaştırır. Eşleşen sözcüğün yerine çağrıdaki ilgili metni yazar. Şekil 9.1.2 bu eşlemeyi MyMacro 4, 5, DX çağrısı için gösterir: birinci parametre p1 4'e, ikinci p2 5'e, üçüncü p3 ise DX'e eşlenir. Gövdedeki MOV CX, p3 satırı böylece MOV CX, DX olur.
MOV CX, p3 satırı MOV CX, DX olur.Burada kritik bir nokta vardır. Assembler parametrenin yazmaç mı, bellek mi, sabit mi olduğuyla ilgilenmez. Yalnız metni değiştirir. Ortaya çıkan satırın geçerli bir 8086 komutu olup olmadığına genişletmeden sonra, sıradan bir satır gibi bakılır. Bu yüzden makro tanımında hata yoktur ama belirli bir çağrıda hata çıkabilir.
Makroyu programın sonuna, prosedürlerin yanına yazmak. Prosedür için doğru olan bu yerleşim makroda hataya yol açar: assembler çağrı satırına geldiğinde makroyu tanımadığı için satırı bilinmeyen komut sayar. Makro tanımları dosyanın başına ya da INCLUDE ile eklenen bir kütüphane dosyasına yazılır.
9.1.1.3Genişletme adım adım
Genişletme en iyi somut bir örnekle anlaşılır. Üç parametreli MyMacro makrosu üç yazmaca birer değer yükler:
MyMacro MACRO p1, p2, p3
MOV AX, p1
MOV BX, p2
MOV CX, p3
ENDM
ORG 100h
MyMacro 1, 2, 3
MyMacro 4, 5, DX
RETAssembler ilk dört satırı okuduğunda kod üretmez. MyMacro adını, üç parametre adını ve üç gövde satırını bir makro tablosuna kaydeder. ORG 100h konum sayacını 0100h'ye kurar. Ardından MyMacro 1, 2, 3 satırı gelir. Assembler MyMacro'yu tablosunda bulur ve parametreleri eşler: p1 = 1, p2 = 2, p3 = 3. Sonra gövdeyi satır satır kopyalar:
MOV AX, p1satırında p1 sözcüğü 1 ile değişir:MOV AX, 1.MOV BX, p2satırıMOV BX, 2olur.MOV CX, p3satırıMOV CX, 3olur.
Bu üç satır, çağrı satırının yerini alır ve sıradan komutlar gibi makine koduna çevrilir. İkinci çağrı MyMacro 4, 5, DX için aynı işlem yeni bir eşlemeyle yinelenir: p1 = 4, p2 = 5, p3 = DX. Ortaya MOV AX, 4, MOV BX, 5 ve MOV CX, DX çıkar. Son satırdaki DX bir sayı değil, bir yazmaç adıdır. Assembler bunu fark etmez, yalnız "DX" metnini yerine koyar. Sonuçta oluşan MOV CX, DX geçerli bir yazmaçtan yazmaca kopyadır.
emu8086 genişletilmiş kodu öykünücü penceresinde şöyle gösterir:
MOV AX, 00001h
MOV BX, 00002h
MOV CX, 00003h
MOV AX, 00004h
MOV BX, 00005h
MOV CX, DX
RETSabitler onaltılık olarak, beş haneyle yazılmıştır; bu yalnız bir gösterim alışkanlığıdır. Önemli olan, iki makro çağrısının altı komuta açılması ve makro adının listede hiç görünmemesidir. Öykünücüde tek adım çalıştırma da bu altı komut üzerinde ilerler. Programcı kaynakta iki satır yazmış, işlemci altı komut yürütmüştür.
Bu komutların makine kodu da hesaplanabilir. Bir 16 bitlik yazmaca anında değer yükleyen MOV r16, imm16 komutu 3 bayttır: işlem kodu B8h + yazmaç numarası ve iki bayt veri. MOV AX, 1 = B8 01 00, MOV BX, 2 = BB 02 00, MOV CX, 3 = B9 03 00. Yazmaçtan yazmaca MOV CX, DX ise 2 bayttır (8B CA). RET tek bayttır (C3). Programın toplam boyutu 5 × 3 + 2 + 1 = 18 bayt, yani 12h bayttır.
9.1.1.4Kod izleme: açılmış kodu çalıştırmak
Açılmış kodun çalışması, sıradan bir programın çalışmasından farksızdır. Yine de bir ayrıntı izleme tablosunu anlamak için önemlidir. emu8086, bir .COM programını belleğe yüklediğinde CX yazmacına programın bayt cinsinden boyutunu yazar. Bu davranış DOS'un program yükleyicisinden gelir. Bu programda boyut 18 bayt olduğu için yükleme anında CX = 0012h'dir. Öteki genel amaçlı yazmaçlar sıfır, SP ise FFFEh'dir. SP'nin gösterdiği FFFEh adresinde 0000h sözcüğü durur; programın sonundaki RET bu sözcüğü alacaktır.
Tablo 9.1.1 açılmış kodun izlemesini verir. İlk üç adım birinci çağrının, sonraki üç adım ikinci çağrının ürettiği komutlardır. Komut sütununun altındaki küçük yazı, komutun hangi makronun açılımından geldiğini gösterir.
| Adım | Adres | Komut | AX | BX | CX | DX | SP | Açıklama |
|---|---|---|---|---|---|---|---|---|
| 1 | 0100h | MOV AX, 1MyMacro açılımı | 0001h | 0000h | 0012h | 0000h | FFFEh | 1. açılım, p1 = 1: AX = 0001h |
| 2 | 0103h | MOV BX, 2MyMacro açılımı | 0001h | 0002h | 0012h | 0000h | FFFEh | p2 = 2: BX = 0002h |
| 3 | 0106h | MOV CX, 3MyMacro açılımı | 0001h | 0002h | 0003h | 0000h | FFFEh | p3 = 3: CX = 0003h; yükleme değeri 0012h silindi |
| 4 | 0109h | MOV AX, 4MyMacro açılımı | 0004h | 0002h | 0003h | 0000h | FFFEh | 2. açılım, p1 = 4 |
| 5 | 010Ch | MOV BX, 5MyMacro açılımı | 0004h | 0005h | 0003h | 0000h | FFFEh | p2 = 5 |
| 6 | 010Fh | MOV CX, DXMyMacro açılımı | 0004h | 0005h | 0000h | 0000h | FFFEh | p3 = DX: yazmaçtan yazmaca kopya, CX = DX = 0000h |
| 7 | 0111h | RETana program | 0004h | 0005h | 0000h | 0000h | 0000h | yığının tepesindeki 0000h → IP; SP = 0000h |
| 8 | 0000h | INT 20h (PSP:0000h)PSP | 0004h | 0005h | 0000h | 0000h | 0000h | PSP:0000h'daki INT 20h programı bitirir |
Tabloda dikkat çeken birkaç nokta vardır. CX'in yükleme değeri 0012h, ilk iki adımda değişmeden durur ve üçüncü adımda 0003h ile silinir. İkinci çağrının son komutu CX'e DX'i kopyalar; DX başlangıçta sıfır olduğundan CX = 0000h olur. Adımların hiçbirinde SP değişmez. Makronun gövdesinde PUSH ya da POP olmadığı sürece makro yığına dokunmaz. Aynı iş bir prosedürle yapılsaydı her CALL, SP'yi 2 azaltır ve dönüş adresini yığına yazardı. Son RET ise makroya ait değildir; ana programın RET'idir. Yığının tepesindeki 0000h sözcüğünü IP'ye alır ve program kesimi önekindeki (PSP) INT 20h komutuna gider.
Bu programın süresi de saat turu (clock cycle) olarak hesaplanabilir. 8086'da MOV r16, imm16 4 tur, MOV r16, r16 2 tur sürer. Altı komut 5 × 4 + 2 = 22 tur tutar. Prosedürle yazılmış bir sürümde bu süreye her çağrı için CALL ve RET süreleri eklenirdi.
9.1.2Parametreler
9.1.2.1Parametre bir metindir: takas tuzağı
Yüksek düzeyli bir dilde fonksiyona verilen parametrenin değeri çağrı anında hesaplanır. Fonksiyona bu değer gider. Makroda böyle bir şey olmaz. Makro parametresi bir değer taşımaz, bir metin taşır. Assembler bu metni gövdedeki her geçtiği yere yazar. Metnin işaret ettiği yazmacın ya da değişkenin değeri ise ancak çalışma anında, o satır yürütüldüğünde okunur. Bu ayrım küçük görünür ama makrolardaki hataların önemli bir kısmı buradan çıkar.
Makro parametresi, makro tanımında bir yer tutucu olarak yazılan biçimsel addır. Çağrıdaki karşılığı bir değer değil, bir metindir. Assembler bu metni gövdede parametrenin geçtiği her yere yazar. Metin bir yazmaç ya da değişken adıysa onun değeri, açılmış satır çalışma anında yürütüldüğünde okunur.
Bir programcının MyMacro'yu AX ile BX'i takas etmek için kullanmak istediğini düşünelim. Makronun ilk satırı AX'e p1'i, ikinci satırı BX'e p2'yi yazdığına göre MyMacro BX, AX, 0 çağrısı "AX ← BX, BX ← AX" yapıyor gibi görünür. Programcı bu çağrıdan sonra AX = 2222h, BX = 1111h bekler. Açılımı yazınca durum değişir:
ORG 100h
MOV AX, 1111h
MOV BX, 2222h
MOV AX, BX ; MyMacro BX, AX, 0
MOV BX, AX ; p2 = AX (bozuk!)
MOV CX, 0
RET| Adım | Adres | Komut | AX | BX | CX | Açıklama |
|---|---|---|---|---|---|---|
| 1 | 0100h | MOV AX, 1111hana program | 1111h | 0000h | 0000h | AX = 1111h |
| 2 | 0103h | MOV BX, 2222hana program | 1111h | 2222h | 0000h | BX = 2222h |
| 3 | 0106h | MOV AX, BXMyMacro açılımı | 2222h | 2222h | 0000h | p1 = BX: AX ← BX; 1111h burada kayboldu |
| 4 | 0108h | MOV BX, AXMyMacro açılımı | 2222h | 2222h | 0000h | p2 = AX: AX'in şimdiki değeri okunur, BX = 2222h |
| 5 | 010Ah | MOV CX, 0MyMacro açılımı | 2222h | 2222h | 0000h | p3 = 0 |
| 6 | 010Dh | RETana program | 2222h | 2222h | 0000h | sonuç AX = BX = 2222h |
| 7 | 0000h | INT 20h (PSP:0000h)PSP | 2222h | 2222h | 0000h | program biter |
Üçüncü adımda AX, BX'in değeriyle, yani 2222h ile dolar. AX'in eski değeri 1111h artık hiçbir yerde yoktur. Dördüncü adımdaki MOV BX, AX, programcının düşündüğü "eski AX"i değil, AX'in o anki değerini okur. Sonuç olarak iki yazmaç da 2222h olur. Bir yüksek düzeyli dilde parametreler çağrı anında hesaplandığı için bu hata oluşmazdı. Makroda ise p2 = AX, "çağrı anındaki AX'in değeri" demek değildir; yalnız "AX" yazısıdır.
Bu tuzağın genel kuralı şudur: makro gövdesinin değiştirdiği bir yazmaç, gövdede o değişiklikten sonra okunan bir parametreye verilmemelidir. MyMacro önce AX'i yazar, sonra p2'yi okur; p2'ye AX vermek bu yüzden yanlıştır. Gerçek bir takas için 8086'nın tek komutluk XCHG AX, BX komutu vardır. Bir makro takas yapacaksa ya XCHG kullanır ya da değerlerden birini önce yığına saklar.
Makro parametresini yüksek düzeyli dildeki değer parametresi gibi düşünmek. TOPLA2 CX, AX gibi bir çağrıda gövde önce AX'e CX'i yazıp sonra ADD AX, AX yaparsa, beklenen CX + AX değil 2 × CX bulunur. Makronun kendisi için kullandığı yazmaçlar belgelenir ve bu yazmaçlar parametre olarak verilmez.
9.1.2.2Parametre türleri ve geçerlilik
Assembler parametreye bir tür vermediği için aynı makro çok farklı biçimlerde çağrılabilir. Hangi çağrının geçerli olduğuna makronun kendisi değil, açılımda oluşan komut karar verir. Aşağıdaki TOPLA makrosu bunu iyi gösterir:
TOPLA MACRO x, y, z
MOV AX, x
ADD AX, y
MOV z, AX
ENDMMakro x ile y'yi toplar ve sonucu z'ye yazar. Neden doğrudan ADD z, x gibi bir şey yazılmadığı sorulabilir. 8086'nın komut kümesi, iki bellek işlenenini tek komutta birleştirmez. ADD, MOV, CMP gibi komutların en çok bir işleneni bellekte olabilir. Bu kısıt donanımdan gelir: komutun ModR/M baytı yalnız bir bellek adresini tanımlayabilir. Bu yüzden makro, toplamayı AX üzerinden yapar. AX burada bir ara yazmaçtır ve makro onu bozar.
| Çağrı | Açılım (sorunlu satır) | Durum |
|---|---|---|
TOPLA a, b, c | MOV AX, a · ADD AX, b · MOV c, AX | geçerli; üç değişken |
TOPLA BX, 5, CX | MOV AX, BX · ADD AX, 5 · MOV CX, AX | geçerli; yazmaç, sabit, yazmaç |
TOPLA a, b, 7 | MOV 7, AX | hata: hedef sabit olamaz |
TOPLA k, b, c (k DB) | MOV AX, k | hata: boyut uyuşmazlığı (16 bit ← 8 bit) |
TOPLA a, b | MOV , AX | hata: eksik işlenen |
Tablo 9.1.3 beş çağrıyı karşılaştırır. İlk iki çağrıda parametreler farklı türdedir ama açılım geçerli komutlar üretir. Üçüncü çağrıda z = 7 olduğu için son satır MOV 7, AX olur. Bir sabite yazılamaz; sabit, komutun içinde gömülü bir sayıdır, yazılabilecek bir bellek hücresi ya da yazmaç değildir. Dördüncü çağrıda k bir bayt değişkendir. MOV AX, k 16 bitlik bir yazmaca 8 bitlik bir bellek hücresini kopyalamaya çalışır. Assembler, değişkenin tanımından boyutunu bildiği için bu uyuşmazlığı yakalar. Beşinci çağrıda üçüncü parametre verilmemiştir. Assembler eksik parametreyi boş metinle değiştirir ve son satır MOV , AX gibi anlamsız bir satıra dönüşür.
Bu hataların hepsi makronun kullanıldığı satırda bildirilir. Oysa nedeni gövdedeki bir satırdır. Hata iletisini anlamanın yolu açılımı zihinde ya da kâğıt üzerinde yazmaktır. Aynı hata iletisi programın birkaç farklı satırında görünüyorsa neden çoğunlukla bu satırların hepsinde kullanılan makronun gövdesindedir.
MASM, eksik parametre sorununu tanım düzeyinde yakalamak için iki olanak sunar. Parametre adının ardına :REQ yazılırsa (ör. z:REQ) o parametre zorunlu olur ve verilmediğinde açık bir hata iletisi üretilir. y:=<1> biçimi ise parametreye bir varsayılan değer verir; çağrıda y yazılmazsa 1 kullanılır. Bu olanaklar MASM'a özgüdür; daha basit assembler'larda eksik parametre her zaman boş metin olarak kalır.
9.1.2.3Kod izleme: TOPLA ile iki açılım
Aşağıdaki programda TOPLA makrosu iki farklı biçimde çağrılır. Birinci çağrıda üç parametre de değişkendir. İkinci çağrıda y yerine bir sabit verilir ve birinci çağrının sonucu girdi olarak kullanılır:
org 100h
TOPLA a, b, c ; c = a + b
TOPLA c, 5, d ; d = c + 5
ret
a dw 1200h
b dw 0345h
c dw ?
d dw ?Açılmış kod altı komut ve bir RET'tir. Değişkenlerin adresleri, kodun boyutuna bağlıdır; bu yüzden önce bayt sayısı hesaplanmalıdır. MOV AX, a komutu AX'e özel kısa bir biçimle kodlanır: A1 işlem kodu ve iki baytlık adres, toplam 3 bayt. ADD AX, b bellek işlenenli genel biçimi kullanır: 03 06 ve iki baytlık adres, toplam 4 bayt. MOV c, AX yine kısa biçimdir (A3 + adres), 3 bayt. Birinci açılım 3 + 4 + 3 = 10 bayttır.
İkinci açılımda ortadaki satır ADD AX, 5'tir. 8086'nın AX'e anında değer ekleyen kısa bir biçimi vardır: 05 işlem kodu ve iki bayt veri, toplam 3 bayt. Bu yüzden ikinci açılım 3 + 3 + 3 = 9 bayttır. Aynı makro, parametreye göre farklı uzunlukta kod üretmiştir. Kod 10 + 9 = 19 bayt, RET ile 20 bayttır. Program 0100h'de başladığına göre a, 0100h + 14h = 0114h adresindedir. b 0116h'de, c 0118h'de, d 011Ah'dedir.
| Adım | Adres | Komut | AX | a | b | c | d | Açıklama |
|---|---|---|---|---|---|---|---|---|
| 1 | 0100h | MOV AX, aTOPLA açılımı | 1200h | 1200h | 0345h | 0000h | 0000h | x = a: AX ← [0114h] |
| 2 | 0103h | ADD AX, bTOPLA açılımı | 1545h | 1200h | 0345h | 0000h | 0000h | y = b: 1200h + 0345h = 1545h |
| 3 | 0107h | MOV c, AXTOPLA açılımı | 1545h | 1200h | 0345h | 1545h | 0000h | z = c: [0118h] ← 1545h |
| 4 | 010Ah | MOV AX, cTOPLA açılımı | 1545h | 1200h | 0345h | 1545h | 0000h | 2. açılım, x = c: AX ← [0118h] |
| 5 | 010Dh | ADD AX, 5TOPLA açılımı | 154Ah | 1200h | 0345h | 1545h | 0000h | y = 5 bir sabit: kısa AX biçimi 05 05 00 |
| 6 | 0110h | MOV d, AXTOPLA açılımı | 154Ah | 1200h | 0345h | 1545h | 154Ah | z = d: [011Ah] ← 154Ah |
| 7 | 0113h | retana program | 154Ah | 1200h | 0345h | 1545h | 154Ah | program biter |
Tablo 9.1.4 izlemenin ayrıntısını verir. Birinci adımda AX, a'nın değeri 1200h ile dolar. İkinci adımda 0345h eklenir: 1200h + 0345h = 1545h. Üçüncü adımda bu toplam c'ye yazılır. İkinci açılım c'yi okur, 5 ekler ve sonucu d'ye yazar: 1545h + 5 = 154Ah. İki açılım aynı gövdeden üretilmiş olsa da birinde y bir bellek adresi, ötekinde bir sabittir. Assembler bu farkı yalnız metinden çıkarır ve uygun makine kodunu seçer.
Bellek erişimleri donanım açısından da incelenebilir. MOV AX, a komutu yürütülürken BIU, DS:0114h, yani fiziksel 07114h adresinden bir sözcük okur. a çift adreste olduğu için okuma tek bir yol döngüsünde (bus cycle) biter. Adres tek olsaydı 8086 sözcüğü iki ayrı bayt okumasıyla getirmek zorunda kalırdı. Makro bu ayrıntıyı değiştirmez. Açılımdaki her komut, elle yazılmış eşinin yaptığı bellek erişimlerini aynen yapar.
Bu örnekten çıkan bir başka ders, makronun AX'i bozmasıdır. Çağıran program, TOPLA'dan sonra AX'te eski bir değer bekliyorsa yanılır. Makronun hangi yazmaçları değiştirdiği, makronun belgesinde açıkça yazılmalıdır. Yazmaç koruma, PUTC makrosunda ayrıca ele alınır.
s1 dw 1234h, s2 dw 0F00Fh için TOPLA s1, s2, s3 çağrısından sonra s3 ve CF ne olur?
Açılım MOV AX, s1, ADD AX, s2, MOV s3, AX'tir. 1234h + F00Fh = 10243h; bu 17 bitlik bir sonuçtur. AX'e alt 16 bit, 0243h sığar ve en yüksek bit CF'ye gider: CF = 1. MOV bayrakları değiştirmediği için CF makrodan sonra da 1 kalır. s3 = 0243h olur. Makro eldeyi hiçbir yere yazmadığı için işaretsiz toplamın doğru sonucu kaybolmuştur. Çağıran program bu durumu makrodan hemen sonra JC ile yakalayabilir.
9.1.2.4İfadeler ve işlem önceliği
Parametre bir metin olduğuna göre çağrıda bir ifade de verilebilir: ALAN 2+3, 4 gibi. Assembler bu ifadeyi önce hesaplamaz; olduğu gibi yerine koyar. İşlem önceliği, ifadenin yerine konduğu satırda yeniden uygulanır. Bu da beklenmedik sonuçlara yol açar:
ALAN MACRO en, boy
MOV AX, en*boy
ENDM
FARK MACRO p, q
MOV CX, p-q
ENDMALAN 2+3, 4 çağrısında en = "2+3", boy = "4" olur. Açılım MOV AX, 2+3*4 satırıdır. Assembler bu ifadeyi derleme anında hesaplar ve çarpma toplamadan önce yapıldığı için 2 + 12 = 14 bulur. Programcının beklediği (2 + 3) × 4 = 20 değeri ortaya çıkmaz. FARK makrosunda da aynı sorun vardır: FARK 10, 4+1 çağrısı MOV CX, 10-4+1 olur ve soldan sağa hesaplanınca CX = 7 bulunur, beklenen 5 değil.
| Çağrı | Açılım | Makine kodu | Sonuç |
|---|---|---|---|
ALAN 2+3, 4 | MOV AX, 2+3*4 | B8 0E 00 | AX = 000Eh (14) |
düzeltilmiş ALAN 2+3, 4 | MOV AX, (2+3)*(4) | B8 14 00 | AX = 0014h (20) |
FARK 10, 4+1 | MOV CX, 10-4+1 | B9 07 00 | CX = 0007h |
düzeltilmiş FARK 10, 4+1 | MOV CX, (10)-(4+1) | B9 05 00 | CX = 0005h |
ALAN BX, 2 | MOV AX, BX*2 | – | hata: * yalnız sabitlerle |
Çözüm, gövdede her parametreyi paranteze almaktır: MOV AX, (en)*(boy) ve MOV CX, (p)-(q). Böylece hangi metin gelirse gelsin, önce kendi içinde hesaplanır. Tablo 9.1.5 iki biçimi makine kodlarıyla karşılaştırır. Dikkat edilirse makine kodunda çarpma ya da toplama komutu yoktur. İfade derleme anında hesaplanmış, sonucu MOV komutunun içine anında değer olarak gömülmüştür. İşlemci yalnız 000Eh ya da 0014h sayısını AX'e yükler.
Son satır bu noktanın sınırını gösterir. ALAN BX, 2 çağrısı MOV AX, BX*2 satırını üretir. BX'in değeri derleme anında bilinmez; yalnız çalışma anında bellidir. Assembler bir yazmacı çarpamaz, bu yüzden hata verir. Bir yazmacın değerini çarpmak için çalışma anında yürüyen bir komut gerekir: MUL ya da 2'nin kuvvetleri için SHL. Derleme anında hesaplanan ifade ile çalışma anında yürüyen komut arasındaki bu ayrım, makroları anlamanın anahtarıdır.
Gövdede parametreleri parantezsiz kullanmak: en*boy yerine (en)*(boy) yazılmalıdır. Makro yalnız tek sayılarla denendiğinde hata görünmez; ifade içeren ilk çağrıda sonuç sessizce yanlış çıkar. Assembler bir uyarı vermez, çünkü 2+3*4 geçerli bir ifadedir.
9.1.3Makro ve prosedür
Makro ile prosedür aynı soruna iki farklı çözümdür: tekrar eden kodu bir ad altında toplamak. Biri işi derleme anında, öteki çalışma anında yapar. Bu fark kod boyutuna, hıza, yığın kullanımına ve etiketlerin davranışına yansır. Bu kesim önce iki yöntemi ayrı ayrı tanımlar, sonra bayt ve saat turu hesabıyla karşılaştırır.
9.1.3.1Prosedürün özellikleri
Prosedür (procedure), PROC ve ENDP direktifleri arasına yazılan, CALL ile çağrılıp RET ile dönen kod bloğudur. Gövdesi belleğin belirli bir adresinde tek kopya olarak durur. Aşağıdaki YAZ_A prosedürü ekrana 'A' yazar:
YAZ_A PROC
PUSH AX
MOV AL, 'A'
MOV AH, 0Eh
INT 10h
POP AX
RET
YAZ_A ENDP
CALL YAZ_A ; 3 bayt
CALL YAZ_A ; 3 baytYakın (near) bir CALL komutu yürütüldüğünde işlemci üç iş yapar. Önce SP'yi 2 azaltır. Sonra CALL'dan sonraki komutun adresini, yani dönüş adresini SS:SP'ye yazar. Son olarak IP'ye prosedürün adresini yükler. RET bunun tersini yapar: SS:SP'deki sözcüğü IP'ye alır ve SP'yi 2 artırır. Dönüş adresinin yığında saklanması sayesinde aynı prosedür programın farklı yerlerinden çağrılabilir; her seferinde doğru yere döner.
Yakın CALL komutu 3 bayttır: E8 işlem kodu ve iki baytlık bağıl uzaklık. Bağıl uzaklık, prosedürün adresinden CALL'dan sonraki komutun adresi çıkarılarak bulunur. Aynı prosedür yüz kez çağrılsa da gövde bir kez yazılır; her çağrı yalnız 3 bayt ekler. Parametreler yazmaçlarla, bellekle ya da yığınla geçirilir. Prosedürün adı makine kodunda bir adrese dönüştüğü için prosedür dosyanın herhangi bir yerinde, çağrıdan sonra da tanımlanabilir.
9.1.3.2Makronun özellikleri
Aynı işi yapan PUTC makrosu ise şöyle yazılır:
PUTC MACRO char
PUSH AX
MOV AL, char
MOV AH, 0Eh
INT 10h
POP AX
ENDM
PUTC 'A' ; 8 bayt
PUTC 'B' ; 8 baytMakro yalnız adı yazılarak kullanılır; CALL yoktur. Parametre, makro adının ardından yazılır. Assembler her kullanımda gövdeyi yeniden yazdığı için iki PUTC, programda iki ayrı 8 baytlık kopya üretir (Şekil 9.1.5). Gövdenin baytları şunlardır: PUSH AX 1 bayt (50), MOV AL, 'A' 2 bayt (B0 41), MOV AH, 0Eh 2 bayt (B4 0E), INT 10h 2 bayt (CD 10), POP AX 1 bayt (58). Toplam 8 bayttır. Makroda RET olmadığına dikkat edilmelidir; makro, çağrılan değil, yerine yazılan bir koddur.
Etiketler konusunda da önemli bir fark vardır. Prosedürün gövdesi tek kopya olduğu için içindeki etiketler de bir kez tanımlanır. Makronun gövdesi ise her kullanımda yeniden yazılır. Gövdede bir etiket varsa ve makro iki kez kullanılırsa aynı etiket iki kez tanımlanmış olur ve assembler Duplicate declaration hatası verir. Bu sorunun çözümü olan LOCAL direktifi kendi kesiminde işlenir.
| Ölçüt | Makro | Prosedür |
|---|---|---|
| Ne zaman işlenir? | derleme anında (metin değiştirme) | çalışma anında (CALL/RET) |
| Kullanım | PUTC 'A' | CALL YAZ_A |
| Kod boyutu | her kullanımda gövde kadar artar | gövde bir kez + her çağrı 3 bayt |
| Hız | CALL/RET ve yığın erişimi yok | her çağrıda CALL + RET süresi |
| Parametre | ad sonrası metin | yazmaç, bellek ya da yığın |
| Yığın | kullanmaz (gövde kullanmadıkça) | dönüş adresi için kullanır |
| Tanım yeri | kullanımdan önce | herhangi bir yerde |
| Bitiş | ENDM | ad ENDP |
| Etiketler | LOCAL gerekir | sorun yok (tek kopya) |
Tablo 9.1.6 iki yöntemi dokuz ölçütle yan yana koyar. Tablonun ilk satırı ötekilerin nedenidir. Makro derleme anında işlendiği için çalışma anına bir maliyet bırakmaz, ama her kullanımda yer kaplar. Prosedür çalışma anında işlendiği için yer kazandırır, ama her çağrıda zaman harcar.
9.1.3.3Boyut hesabı ve başa baş noktası
Kod boyutunu kullanım sayısına göre hesaplamak için iki basit formül yeter. Gövdesi n bayt olan bir iş k yerde kullanılsın. Makroda gövde k kez yazılır: boyut k · n bayttır. Prosedürde gövde bir kez yazılır, sonuna 1 baytlık RET eklenir ve her kullanım 3 baytlık bir CALL'dır: boyut (n + 1) + 3k bayttır.
makro = k · n prosedür = (n + 1) + 3k
PUTC'nin gövdesi n = 8 bayttır. Tek kullanımda makro 8 bayt, prosedür 8 + 1 + 3 = 12 bayt tutar; makro daha küçüktür. On kullanımda makro 80 bayta, prosedür 9 + 30 = 39 bayta çıkar. Yüz kullanımda fark büyür: makro 800 bayt, prosedür 9 + 300 = 309 bayttır. Şekil 9.1.6 bu üç durumu çubuklarla gösterir. Makronun çubuğu kullanım sayısıyla doğru orantılı büyür; prosedürün çubuğu 9 baytlık sabit bir parçanın üstüne her kullanımda yalnız 3 bayt ekler.
Makronun prosedürden büyük olmaya başladığı kullanım sayısına başa baş noktası denebilir. Prosedürün daha küçük olması için k · n > (n + 1) + 3k olmalıdır. Terimler toplanınca k · (n − 3) > n + 1 bulunur. Bu eşitsizlikten iki sonuç çıkar.
- n ≤ 3 ise sol taraf sıfır ya da negatiftir ve eşitsizlik hiçbir k için sağlanmaz. Gövde bir CALL kadar kısaysa makro her zaman daha küçüktür. Bu durumda prosedür kod boyutunu azaltmaz, yalnız RET ve çağrı süresini ekler.
- n > 3 ise başa baş noktası k > (n + 1) / (n − 3)'tür. n = 8 için k > 9/5 = 1,8, yani iki kullanımdan itibaren prosedür daha küçüktür: 16 bayta karşı 15 bayt. n = 5 için k > 3, yani dört kullanımdan itibaren. n = 4 için k > 5; beş kullanımda ikisi de 20 bayttır, altı kullanımda prosedür 23 bayta karşı 24 bayt ile öne geçer.
PUTC makrosu 50 kez kullanılıyor. Aynı iş prosedürle yapılsaydı kod boyutları ne olurdu?
Makro: 50 × 8 = 400 bayt. Prosedür: gövde ve RET bir kez (8 + 1 = 9 bayt), CALL'lar 50 × 3 = 150 bayt; toplam 159 bayt. Prosedür 241 bayt daha küçüktür. Sık yapılan bir hesap hatası, prosedür için yalnız CALL'ları (150 bayt) saymak ya da makro kopyasını RET ile 9 bayt saymaktır. Makroda RET yoktur.
Bu hesap neden önemlidir? Bir .COM programının kodu, verisi ve yığını tek bir 64 KB'lık kesime sığmak zorundadır. Gömülü bir sistemde program küçük bir ROM'a yazılır ve her bayt bir maliyettir. Gövdesi uzun bir makronun yüzlerce yerde kullanılması bu sınırları hızla zorlar.
9.1.3.4Hız: CALL, RET ve önden getirme kuyruğu
Hız tarafında tablo tersine döner. Prosedürün her çağrısı, işin kendisine ek olarak bir CALL ve bir RET yürütür. Intel'in 8086 veri sayfasına göre yakın, doğrudan bir CALL 19 saat turu, yakın bir RET 8 saat turu sürer. Her çağrı böylece 27 tur ekler. 4,77 MHz'lik bir 8086'da bir tur yaklaşık 209,6 ns'dir; 27 tur yaklaşık 5,66 µs eder. 5 MHz'de bir tur 200 ns, 27 tur 5,4 µs'dir.
Bu sayının arkasında donanımda olan biten vardır. CALL komutu dönüş adresini yığına yazar; bu bir bellek yazma yol döngüsüdür. Bir yol döngüsü en az dört saat turu (T1–T4) sürer. RET de yığından bir sözcük okur; bu da bir okuma yol döngüsüdür. Daha önemlisi, her iki komut da programın akışını değiştirir. 8086'da yürütme birimi (EU) bir komutu yürütürken veri yolu arabirim birimi (BIU) sonraki komut baytlarını önceden getirir. Bu baytlar altı baytlık önden getirme kuyruğunda (prefetch queue) bekler. Akış bir atlamayla değiştiğinde kuyruktaki baytlar işe yaramaz hâle gelir. BIU kuyruğu boşaltır ve yeni adresten getirmeye baştan başlar. EU, ilk komut baytı gelene kadar bekler. CALL ve RET'in uzun sürmesinin bir nedeni budur.
Makronun açılımında ise akış düz bir çizgidir. Komutlar bellekte art arda durur, BIU onları sırayla getirir ve kuyruk hiç boşaltılmaz. Yığına dönüş adresi yazılmaz, yığından okunmaz. PUTC'nin gövdesi kendi başına yaklaşık 78 tur sürer: PUSH 11, iki MOV 4'er, INT 51, POP 8 tur. Bu süreye BIOS'un kendi yordamı ve onun IRET'i eklenir. Prosedür sürümünde aynı işe her çağrıda 27 tur daha eklenir.
| Komut | Bayt | Saat turu | Bellek/yığın erişimi |
|---|---|---|---|
PUSH AX | 1 | 11 | 1 yazma (yığın) |
MOV AL, 'A' | 2 | 4 | yok |
MOV AH, 0Eh | 2 | 4 | yok |
INT 10h | 2 | 51 | 3 yazma (FLAGS, CS, IP) + vektör okuma |
POP AX | 1 | 8 | 1 okuma (yığın) |
| makro kopyası toplam | 8 | 78 | |
CALL (yakın, doğrudan) | 3 | 19 | 1 yazma (dönüş adresi) |
RET (yakın) | 1 | 8 | 1 okuma (dönüş adresi) |
| prosedür çağrısı toplam | 3 (+ 9 bir kez) | 105 |
Tablo 9.1.7 iki sürümü bayt ve tur olarak karşılaştırır. Ek süre çağrı başına küçüktür ama birikir. Bir döngüde 10 000 kez çağrılan bir prosedür, 4,77 MHz'de 10 000 × 27 = 270 000 tur, yani yaklaşık 56,6 ms ek süre getirir. Kısa bir işte bu ek süre işin kendisinden bile uzun olabilir. Örneğin iki komutluk, toplam 6–8 tur süren bir iş prosedüre konursa, her çağrı süreyi dört beş katına çıkarır.
9.1.3.5Seçim ölçütleri
Boyut ve hız birlikte tartılınca genel kural ortaya çıkar. Kısa, sık çalışan ve hız gerektiren kod makroya; uzun ve çok yerde kullanılan kod prosedüre uygundur. Beş tipik durum bu kuralı somutlaştırır:
- Ekrana tek karakter yazmak, 200 yerde. Gövde kısadır (8 bayt) ama 200 kopya 1600 bayt eder. Prosedür 9 + 600 = 609 bayt tutar. Boyut önemliyse prosedür, hız önemliyse makro seçilir. Ekrana yazma zaten BIOS çağrısı içerdiği için 27 turluk ek çoğu zaman önemsizdir; burada prosedür daha akıllıca bir seçimdir.
- İki yazmacı takas eden tek satırlık iş.
XCHGzaten tek komuttur. Bir makro okunabilirliği artırabilir; prosedür ise tek komutluk işe 27 turluk ve 4 baytlık yük ekler. Makro seçilir. - 300 baytlık bir sıralama algoritması, 3 yerde. Makro 900 bayt, prosedür yaklaşık 300 + 9 bayt tutar. Sıralamanın süresi yanında 27 tur önemsizdir. Açıkça prosedür.
- Sıkı bir döngüde her turda yapılan iki komutluk iş. CALL/RET süresi işin kendisinden büyüktür. Döngü binlerce kez döndüğünde fark ölçülebilir hâle gelir. Makro.
- Parametreleri yazmaçlara koyup ortak bir kodu çağırmak. İki yöntem birlikte kullanılır: makro parametreleri yazmaçlara yerleştirir ve prosedürü çağırır. Kullanım makro kadar rahat, boyut prosedür kadar küçük olur. emu8086.inc kütüphanesi de bu düzeni kullanır.
9.1.3.6Kod izleme: makro hazırlar, prosedür yapar
Beşinci durumu küçük bir programla izleyelim. YAZ_RAKAM makrosu bir rakamı AL'ye koyar ve rakam_yaz prosedürünü çağırır. Prosedür rakamı ASCII koduna çevirip ekrana yazar:
YAZ_RAKAM MACRO d
mov al, d
call rakam_yaz
ENDM
org 100h
YAZ_RAKAM 4
YAZ_RAKAM 7
ret
rakam_yaz:
add al, '0' ; rakam → ASCII
mov ah, 0Eh
int 10h
retHer makro kopyası yalnız 5 bayttır: 2 baytlık MOV AL, imm8 ve 3 baytlık CALL. Birinci kopya 0100h–0104h'de, ikincisi 0105h–0109h'da durur. Ana programın RET'i 010Ah'dedir ve prosedür 010Bh'de başlar. Prosedürün gövdesi 2 + 2 + 2 + 1 = 7 bayttır. Makro on yerde kullanılsaydı kod 10 × 5 + 7 = 57 bayt tutardı. Gövdenin tamamı (MOV AL, ADD AL, MOV AH, INT) makroda olsaydı her kopya 8 bayt, on kopya 80 bayt olurdu.
| Adım | Adres | Komut | AL | AH | SP | [SS:SP] | Sonraki IP | Ekran | Açıklama |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 0100h | mov al, 4YAZ_RAKAM açılımı | 04h | 00h | FFFEh | 0000h | 0102h | – | 1. açılım: AL ← 4 |
| 2 | 0102h | call rakam_yazYAZ_RAKAM açılımı | 04h | 00h | FFFCh | 0105h | 010Bh | – | dönüş adresi 0105h yığına; IP ← 010Bh |
| 3 | 010Bh | add al, '0'rakam_yaz prosedürü | 34h | 00h | FFFCh | 0105h | 010Dh | – | 4 + 30h = 34h ('4') |
| 4 | 010Dh | mov ah, 0Ehrakam_yaz prosedürü | 34h | 0Eh | FFFCh | 0105h | 010Fh | – | AH = 0Eh: teletype |
| 5 | 010Fh | int 10hrakam_yaz prosedürü | 34h | 0Eh | FFFCh | 0105h | 0111h | 4 | ekrana 4 |
| 6 | 0111h | retrakam_yaz prosedürü | 34h | 0Eh | FFFEh | 0000h | 0105h | 4 | 0105h yığından IP'ye |
| 7 | 0105h | mov al, 7YAZ_RAKAM açılımı | 07h | 0Eh | FFFEh | 0000h | 0107h | 4 | 2. açılım: AL ← 7 |
| 8 | 0107h | call rakam_yazYAZ_RAKAM açılımı | 07h | 0Eh | FFFCh | 010Ah | 010Bh | 4 | dönüş adresi bu kez 010Ah |
| 9 | 010Bh | add al, '0'rakam_yaz prosedürü | 37h | 0Eh | FFFCh | 010Ah | 010Dh | 4 | 37h ('7') |
| 10 | 010Dh | mov ah, 0Ehrakam_yaz prosedürü | 37h | 0Eh | FFFCh | 010Ah | 010Fh | 4 | |
| 11 | 010Fh | int 10hrakam_yaz prosedürü | 37h | 0Eh | FFFCh | 010Ah | 0111h | 47 | ekranda 47 |
| 12 | 0111h | retrakam_yaz prosedürü | 37h | 0Eh | FFFEh | 0000h | 010Ah | 47 | 010Ah'ye dönüş |
| 13 | 010Ah | retana program | 37h | 0Eh | 0000h | – | 0000h | 47 | ana programın RET'i |
Tablo 9.1.8 ve Şekil 9.1.7 birlikte okunmalıdır. İkinci adımda CALL yürütülür. Dönüş adresi, CALL'dan sonraki komutun adresi olan 0105h'dir. Bu sözcük SS:FFFCh'ye yazılır; SS = 0700h olduğundan fiziksel adres 07000h + FFFCh = 16FFCh'dir. SP = FFFCh olur ve IP 010Bh'ye atlar. Prosedür 4'e 30h ekleyip ASCII '4' (34h) elde eder ve INT 10h ile yazar. Altıncı adımdaki RET, yığının tepesindeki 0105h'yi IP'ye alır ve SP'yi FFFEh'ye geri getirir.
İkinci kopya aynı prosedürü çağırır ama dönüş adresi bu kez 010Ah'dir. Aynı tek gövde, iki farklı yerden çağrılmış ve her seferinde doğru yere dönmüştür. Ekranda 47 görünür. Son RET ana programındır ve yığının tepesindeki 0000h'yi alarak programı bitirir. Makro bu düzende parametreyi hazırlayan ince bir kılıf görevi görür; işin kendisi prosedürde, tek kopyadır.
9.1.3.7Kod izleme: makronun içindeki RET
Prosedür alışkanlığıyla makronun sonuna RET yazmak, makrolarla ilgili en öğretici hatalardan biridir. Aşağıdaki YAZ makrosu bir karakter yazar ve RET ile biter:
YAZ MACRO c
MOV AL, c
MOV AH, 0Eh
INT 10h
RET ; hatalı
ENDM
org 100h
YAZ 'A'
YAZ 'B'
retProgram derlenir; assembler açısından RET de sıradan bir komuttur. Açılmış kodda iki YAZ kopyası ve her birinin sonunda bir RET vardır. Programcı ekranda AB bekler.
| Adım | Adres | Komut | AX | SP | Sonraki IP | Ekran | Açıklama |
|---|---|---|---|---|---|---|---|
| 1 | 0100h | MOV AL, 'A'YAZ açılımı | 0041h | FFFEh | 0102h | – | 1. açılım: AL ← 'A' |
| 2 | 0102h | MOV AH, 0EhYAZ açılımı | 0E41h | FFFEh | 0104h | – | AX = 0E41h |
| 3 | 0104h | INT 10hYAZ açılımı | 0E41h | FFFEh | 0106h | A | ekrana A |
| 4 | 0106h | RETYAZ açılımı | 0E41h | 0000h | 0000h | A | makronun RET'i yığının tepesindeki 0000h'yi alır |
| 5 | 0000h | INT 20h (PSP:0000h)PSP | 0E41h | 0000h | – | A | PSP'deki INT 20h programı bitirir; 0107h'den sonrası hiç çalışmaz |
Tablo 9.1.9 beklenenin olmadığını gösterir. Birinci kopya 'A'yı yazar. Dördüncü adımdaki RET, makronun RET'idir. Makroya CALL ile gelinmediği için yığında bir dönüş adresi yoktur. RET yine de yığının tepesinde ne bulursa onu IP'ye alır. Bu .COM programında tepede, DOS'un yerleştirdiği 0000h sözcüğü durur. IP = 0000h olur ve işlemci PSP'nin başındaki INT 20h komutuna gider. Program biter. YAZ 'B'nin kopyası ve ana programın RET'i hiç çalışmaz; ekranda yalnız A vardır.
Bu programın şanslı bir sonu vardır: yığının tepesinde tesadüfen programı bitiren bir adres durur. Makro bir prosedürün içinde ya da yığına bir şey itildikten sonra kullanılsaydı RET, yığındaki rastgele bir sözcüğe atlardı ve program kilitlenebilirdi. RET yalnız CALL ile girilen bir prosedürde anlamlıdır.
Makro gövdesinin sonuna RET yazmak. Makro CALL ile çağrılmaz; gövdedeki RET, makroyu kullanan kodun dönüş adresini (ya da yığında ne varsa onu) alır. Makro, ENDM'e gelindiğinde kendiliğinden "biter": açılımın son komutundan sonra, çağrı satırını izleyen komut yürütülür.
9.1.3.8EQU, makro ve prosedür
Assembler'ın metin üzerinde çalışan bir başka aracı EQU direktifidir. SATIR EQU 80 yazıldığında assembler, SATIR adını gördüğü her yere 80 yazar. Bu, tek satırlık ve parametresiz bir makroya benzer. Üç aracın karşılaştırması Tablo 9.1.10'de verilmiştir.
| Ölçüt | EQU | Makro | Prosedür |
|---|---|---|---|
| Ne yerine konur? | tek bir değer ya da ifade | bir ya da daha çok satır | hiçbir şey; CALL ile gidilir |
| Parametre | yok | var (metin) | yazmaç, yığın ya da bellek |
| Ne zaman? | derleme | derleme | çalışma |
| Bellekte | yer kaplamaz | her kullanımda gövde kadar | tek kopya + her çağrıda 3 bayt |
| Örnek | SATIR EQU 80 | PUTC 'A' | CALL PRINT_NUM |
EQU ile makronun ortak noktası, ikisinin de assembler'ın kaynak üzerinde yaptığı metin işlemleri olmasıdır. İşlemci ikisini de hiç görmez; yalnız ürettikleri komutları yürütür. Bir EQU sabiti tek başına bayt bile üretmez; ancak bir komutun içinde kullanıldığında o komutun anında değeri olur. Seçim kuralı basittir. Tek bir sayıya ad vermek için EQU, birkaç komutluk kısa kalıp için makro, uzun ya da yaygın bir iş için prosedür kullanılır.
9.1.4Etiketler ve LOCAL
9.1.4.1Aynı etiket iki kez
Döngü ya da koşullu atlama içeren her makronun gövdesinde bir etiket (label) bulunur. Etiket, assembler'ın bir adrese verdiği addır. Bir programda her etiket yalnız bir kez tanımlanabilir; aksi hâlde aynı ad iki farklı adresi gösterir ve atlamanın hedefi belirsizleşir. Makronun gövdesi her kullanımda yeniden yazıldığı için bu kural makrolarda kolayca çiğnenir. Basit bir bekleme döngüsü bunu gösterir:
BEKLE MACRO
ilk: DEC CX
JNZ ilk
ENDM
MOV CX, 5
BEKLE
MOV CX, 9
BEKLEMakro bir kez kullanılsaydı sorun olmazdı. İki kullanımdan sonra assembler'ın gördüğü kod şudur:
MOV CX, 5
ilk: DEC CX ; 1. kopya
JNZ ilk
MOV CX, 9
ilk: DEC CX ; 2. kopya !
JNZ ilkilk etiketi. JNZ ilk iki adresten hangisine gideceğini bilemez; assembler Duplicate declaration hatası verir.İkinci ilk: satırına gelindiğinde assembler bu adı sembol tablosunda zaten bulur. emu8086 Duplicate declaration (yinelenen bildirim) hatası verir ve program derlenmez. Hata iletisi ikinci BEKLE satırını gösterir; programcı o satıra baktığında yalnız makronun adını görür. Hatanın nedenini anlamak için yine açılımı düşünmek gerekir.
9.1.4.2LOCAL direktifi
Sorunun çözümü, her açılımda etikete farklı bir ad vermektir. Bunu programcı elle yapamaz, çünkü gövde tek bir yerde yazılıdır. Assembler bu işi LOCAL direktifiyle üstlenir.
LOCAL direktifi (LOCAL directive), makro gövdesinin ilk satırına yazılır ve ardından gelen adları makroya yerel kılar. Assembler her genişletmede bu adların yerine o ana kadar kullanılmamış, benzersiz bir ad koyar. MASM ve emu8086 bu adları ??0000, ??0001, ??0002… biçiminde, programdaki bütün açılımlar boyunca artan bir sayaçla üretir.
MyMacro2 MACRO
LOCAL label1, label2
CMP AX, 2
JE label1
CMP AX, 3
JE label2
label1:
INC AX
label2:
ADD AX, 2
ENDMMyMacro2 iki yerel etiket bildirir. İlk kullanımda label1 ??0000, label2 ??0001 olur. İkinci kullanımda sayaç kaldığı yerden sürer: label1 ??0002, label2 ??0003 olur. Sayaç makroya değil programa aittir; başka bir makronun yerel etiketleri de aynı diziden numara alır. ?? ile başlayan adlar bilerek seçilmiştir: programcının kendi etiketiyle çakışma olasılığı yoktur.
LOCAL'ın gövdenin ilk satırı olması gerekir; MACRO satırından hemen sonra gelmelidir. Assembler, yerel adları gövdenin geri kalanına uygulamadan önce bilmek ister. LOCAL yalnız etiketler için değil, makronun içinde tanımlanan değişken ve prosedür adları için de kullanılır. Makro bir dizgiyi kendi içinde tanımlıyorsa (PRINT makrosunda olduğu gibi), dizginin adı da yerel olmalıdır.
İki kullanımlı bir programın listesi bu adlandırmayı ve adresleri birlikte gösterir:
0100 3D 02 00 CMP AX, 2 ; 1. MyMacro2
0103 74 05 JE ??0000
0105 3D 03 00 CMP AX, 3
0108 74 01 JE ??0001
010A 40 ??0000: INC AX
010B 05 02 00 ??0001: ADD AX, 2
010E 3D 02 00 CMP AX, 2 ; 2. MyMacro2
0111 74 05 JE ??0002
0113 3D 03 00 CMP AX, 3
0116 74 01 JE ??0003
0118 40 ??0002: INC AX
0119 05 02 00 ??0003: ADD AX, 2
011C C3 RETKoşullu atlamaların makine kodu da öğreticidir. JE ??0000 = 74 05: 74h işlem kodu, 05h ise bağıl uzaklıktır. Atlama, JE'den sonraki komutun adresine (0105h) 5 eklenerek bulunur: 0105h + 5 = 010Ah. İkinci kopyadaki JE ??0002 de 74 05'tir: 0113h + 5 = 0118h. İki kopyanın makine kodu baytı baytına aynıdır, çünkü 8086'nın kısa atlamaları adresi değil uzaklığı saklar. Farklı olan, etiketlerin adları ve gösterdikleri mutlak adreslerdir.
9.1.4.3Kod izleme: MyMacro2
MyMacro2'nin işi AX'in değerine göre değişir. AX = 2 ise label1'e atlar, INC ve ADD çalışır, AX 2 artı 1 artı 2 = 5 olur. AX = 3 ise label2'ye atlar, yalnız ADD çalışır, yine 5 olur. Başka her değerde iki atlama da gerçekleşmez ve akış label1'e düşer; AX 3 artar. MOV AX, 2 ile başlayan programın izlemesi Tablo 9.1.11'dedir.
ORG 100h
MOV AX, 2 ; MyMacro2 öncesi
CMP AX, 2 ; MyMacro2 başlar
JE ??0000
CMP AX, 3
JE ??0001
??0000:
INC AX
??0001:
ADD AX, 2 ; MyMacro2 biter
RET| Adım | Adres | Komut | AX | ZF | SF | CF | Sonraki IP | Açıklama |
|---|---|---|---|---|---|---|---|---|
| 1 | 0100h | MOV AX, 2ana program | 0002h | 0 | 0 | 0 | 0103h | AX = 2 |
| 2 | 0103h | CMP AX, 2MyMacro2 açılımı | 0002h | 1 | 0 | 0 | 0106h | 2 − 2 = 0: ZF = 1 |
| 3 | 0106h | JE ??0000MyMacro2 açılımı | 0002h | 1 | 0 | 0 | 010Dh | ZF = 1: ??0000'a (label1) atlanır |
| 4 | 010Dh | INC AXMyMacro2 açılımı | 0003h | 0 | 0 | 0 | 010Eh | AX = 3 |
| 5 | 010Eh | ADD AX, 2MyMacro2 açılımı | 0005h | 0 | 0 | 0 | 0111h | AX = 5 |
| 6 | 0111h | RETana program | 0005h | 0 | 0 | 0 | 0000h | program biter |
İkinci adımda CMP AX, 2, AX'ten 2 çıkarır ama sonucu bir yere yazmaz; yalnız bayrakları kurar. 2 − 2 = 0 olduğundan ZF = 1 olur. Üçüncü adımdaki JE ZF'ye bakar ve ??0000'a, yani 010Dh'ye atlar. Sonraki IP sütunu bu atlamayı gösterir: 0108h yerine 010Dh. CMP AX, 3 ve ikinci JE hiç yürütülmez. INC AX 3'ü, ADD AX, 2 ise 5'i verir. Programın başında AX = 3 olsaydı ilk JE atlamaz, ikinci JE label2'ye atlar ve sonuç yine 5 olurdu. AX = 7 olsaydı iki atlama da gerçekleşmez, INC ve ADD çalışır ve AX = 10 olurdu. AX = 0 ile başlayan bir program ise 3 bulurdu.
9.1.4.4Kod izleme: BEKLE'nin iki kopyası
BEKLE makrosuna LOCAL ilk eklendiğinde hata ortadan kalkar:
BEKLE MACRO
LOCAL ilk
ilk: DEC CX
JNZ ilk
ENDM
org 100h
mov cx, 3
BEKLE
mov cx, 2
BEKLE
retBirinci kopyada ilk etiketi ??0000 adını alır ve 0103h'dedir. İkinci kopyada ??0001 olur ve 0109h'dedir. Her kopya kendi etiketine döner.
| Adım | Adres | Komut | CX | ZF | Sonraki IP | Açıklama |
|---|---|---|---|---|---|---|
| 1 | 0100h | mov cx, 3ana program | 0003h | 0 | 0103h | 1. kopyanın sayacı |
| 2 | 0103h | ??0000: DEC CXBEKLE açılımı | 0002h | 0 | 0104h | 1. tur: CX = 2 |
| 3 | 0104h | JNZ ??0000BEKLE açılımı | 0002h | 0 | 0103h | ZF = 0: ??0000'a (0103h) dön |
| 4 | 0103h | ??0000: DEC CXBEKLE açılımı | 0001h | 0 | 0104h | 2. tur |
| 5 | 0104h | JNZ ??0000BEKLE açılımı | 0001h | 0 | 0103h | yine 0103h |
| 6 | 0103h | ??0000: DEC CXBEKLE açılımı | 0000h | 1 | 0104h | 3. tur: CX = 0, ZF = 1 |
| 7 | 0104h | JNZ ??0000BEKLE açılımı | 0000h | 1 | 0106h | atlamaz; 1. kopya bitti |
| 8 | 0106h | mov cx, 2ana program | 0002h | 1 | 0109h | 2. kopyanın sayacı |
| 9 | 0109h | ??0001: DEC CXBEKLE açılımı | 0001h | 0 | 010Ah | 1. tur: CX = 1 |
| 10 | 010Ah | JNZ ??0001BEKLE açılımı | 0001h | 0 | 0109h | ??0001'e (0109h) dön, 0103h'ye değil |
| 11 | 0109h | ??0001: DEC CXBEKLE açılımı | 0000h | 1 | 010Ah | CX = 0, ZF = 1 |
| 12 | 010Ah | JNZ ??0001BEKLE açılımı | 0000h | 1 | 010Ch | atlamaz |
| 13 | 010Ch | retana program | 0000h | 1 | 0000h | program biter; toplam 5 DEC |
JNZ ??0001 0109h'ye döner; birinci kopyanın 0103h'deki etiketine değil.Tablo 9.1.12'de birinci kopya üç tur döner. DEC CX her turda CX'i bir azaltır ve sonuca göre ZF'yi kurar. CX sıfırdan farklı kaldıkça ZF = 0'dır ve JNZ 0103h'ye döner. Üçüncü turda CX = 0 olur, ZF = 1 olur ve JNZ atlamaz. İkinci kopya CX = 2 ile iki tur döner ve her seferinde 0109h'ye döner (Şekil 9.1.9). Toplam beş DEC yürütülür. DEC'in CF'ye dokunmadığı, yalnız ZF, SF, OF, AF ve PF'yi değiştirdiği de anımsanmalıdır.
Bekleme süresi saat turu olarak hesaplanabilir. DEC r16 2 tur sürer. JNZ atladığında 16, atlamadığında 4 tur sürer; atlamada önden getirme kuyruğu boşaltıldığı için fark büyüktür. CX = 3 ile birinci kopya 2 × (2 + 16) + (2 + 4) = 42 tur sürer. Böyle bir döngü, işlemci saatine bağlı kaba bir gecikme üretir.
9.1.4.5Kod izleme: BUYUK ve işaretli karşılaştırma
Son örnek, LOCAL'ı işaretli karşılaştırmayla birleştirir. BUYUK makrosu p ile q'dan büyük olanı r'ye yazar:
BUYUK MACRO p, q, r
LOCAL ok
MOV AX, p
CMP AX, q
JGE ok
MOV AX, q
ok: MOV r, AX
ENDM
org 100h
BUYUK x, y, m1
BUYUK y, 4, m2
ret
x dw 7
y dw -3
m1 dw ?
m2 dw ?Birinci çağrıda üç parametre de değişkendir. İkincisinde q bir sabittir; bu yüzden CMP AX, 4 ve MOV AX, 4 anında değer biçimini kullanır. y = −3, ikiye tümleyen gösterimde FFFDh olarak saklanır.
| Adım | Adres | Komut | AX | SF | OF | CF | ZF | m1 | m2 | Açıklama |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 0100h | MOV AX, xBUYUK açılımı | 0007h | 0 | 0 | 0 | 0 | 0000h | 0000h | p = x: AX = 7 |
| 2 | 0103h | CMP AX, yBUYUK açılımı | 0007h | 0 | 0 | 1 | 0 | 0000h | 0000h | 7 − (−3) = 10: SF = 0, OF = 0 |
| 3 | 0107h | JGE ??0000BUYUK açılımı | 0007h | 0 | 0 | 1 | 0 | 0000h | 0000h | SF = OF: ??0000'a atla |
| 4 | 010Ch | ??0000: MOV m1, AXBUYUK açılımı | 0007h | 0 | 0 | 1 | 0 | 0007h | 0000h | m1 = 7 |
| 5 | 010Fh | MOV AX, yBUYUK açılımı | FFFDh | 0 | 0 | 1 | 0 | 0007h | 0000h | p = y: AX = FFFDh (−3) |
| 6 | 0112h | CMP AX, 4BUYUK açılımı | FFFDh | 1 | 0 | 0 | 0 | 0007h | 0000h | −3 − 4 = −7: SF = 1, OF = 0 |
| 7 | 0115h | JGE ??0001BUYUK açılımı | FFFDh | 1 | 0 | 0 | 0 | 0007h | 0000h | SF ≠ OF: atlamaz |
| 8 | 0117h | MOV AX, 4BUYUK açılımı | 0004h | 1 | 0 | 0 | 0 | 0007h | 0000h | q = 4 büyük olan |
| 9 | 011Ah | ??0001: MOV m2, AXBUYUK açılımı | 0004h | 1 | 0 | 0 | 0 | 0007h | 0004h | m2 = 4 |
| 10 | 011Dh | retana program | 0004h | 1 | 0 | 0 | 0 | 0007h | 0004h | program biter |
İkinci adımdaki CMP AX, y, 7 − (−3) işlemini yapar. İşaretli yorumla sonuç 10'dur: pozitiftir (SF = 0) ve taşma yoktur (OF = 0). JGE (büyük ya da eşitse atla) işaretli karşılaştırmanın sonucuna SF = OF koşuluyla bakar. Koşul sağlandığından ??0000'a atlanır ve m1 = 7 olur. İşaretsiz yorumla aynı çıkarma 0007h − FFFDh'dir; küçükten büyük çıkarıldığı için ödünç doğar ve CF = 1 olur. JGE CF'ye bakmadığı için bu bayrak burada önemsizdir.
İkinci açılımda AX = FFFDh (−3) ve CMP AX, 4 −3 − 4 = −7 sonucunu verir: SF = 1, OF = 0. SF ≠ OF olduğu için JGE atlamaz. Akış MOV AX, 4 ile sürer ve m2 = 4 olur. İkinci kopyanın etiketi ??0001'dir; birincisiyle çakışmaz.
Bu örnekte koşullu atlamanın seçimi önemlidir. JGE yerine işaretsiz karşılaştıran JAE kullanılsaydı ikinci karşılaştırma FFFDh ile 4'ü işaretsiz sayılar olarak ele alırdı. FFFDh = 65 533, 4'ten büyüktür; ödünç yoktur (CF = 0) ve JAE atlar. m2'ye 4 yerine FFFDh, yani −3 yazılırdı. Makro aynı kalsa da tek bir koşullu atlama komutu sonucun doğruluğunu belirler. İşaretli sayılarla JG, JGE, JL, JLE; işaretsiz sayılarla JA, JAE, JB, JBE kullanılır.
Etiket içeren bir makroyu bir kez deneyip doğru çalıştığını görünce LOCAL'ı unutmak. Hata ancak makro ikinci kez kullanıldığında ortaya çıkar; o zaman da hata iletisi gövdeyi değil kullanım satırını gösterir. Etiketi olan her makroya, kaç kez kullanılacağından bağımsız olarak LOCAL yazılır.
9.1.5PUTC ve yazmaçların korunması
9.1.5.1PUTC makrosu
PUTC, ekrana tek bir karakter yazan kısa bir makrodur. emu8086.inc kütüphanesindeki PUTC makrosunun aynısıdır:
; karakter yazar, imleci ilerletir
PUTC MACRO char
push ax
mov al, char
mov ah, 0eh
int 10h
pop ax
ENDM
ORG 100h
START:
PUTC 'A'
PUTC 'B'
RETMakronun asıl işini INT 10h yapar. AH = 0Eh, BIOS'un teletype (daktilo benzeri) çıkış hizmetidir. AL'deki karakter imlecin bulunduğu yere yazılır ve imleç bir sağa ilerler. Satır sonunda imleç alt satıra geçer; ekranın sonunda ekran bir satır kaydırılır. 0Dh (satır başı), 0Ah (alt satır), 07h (zil) ve 08h (geri silme) gibi denetim karakterleri yazılmaz, yorumlanır.
Hizmet AH ve AL'yi kullandığı için makro AX'i değiştirmek zorundadır. Makronun başındaki push ax ve sonundaki pop ax bu değişikliği çağırandan gizler. Makrodan çıkıldığında AX, makroya girilmeden önceki değerine döner. Makronun görünmez bir yan etkisi kalmaz. Bu ilke bütün kütüphane makroları için geçerlidir: makro kendi işi için yazmaç kullanabilir ama çıkarken onları eski hâline getirir.
Yazmaçların korunması (register preservation), bir makronun ya da prosedürün değiştirdiği yazmaçları başta PUSH ile saklayıp sonda ters sırada POP ile geri yüklemesidir. Böylece çağıranın yazmaçlarındaki değerler kullanım boyunca bozulmaz; yalnız sonuç döndürmek için açıkça belirtilen yazmaçlar değişir.
Programın sonundaki RET de önemlidir. İki PUTC kopyasından sonra bellekte programın kodu biter. RET olmasaydı işlemci son POP AX'ten sonraki baytları da komut sanıp yürütmeye devam ederdi. İşlemci kod ile veriyi ayırt etmez; IP'nin gösterdiği her baytı bir işlem kodu olarak çözer. Bellekte ne kaldıysa rastgele komutlar olarak çalışır ve sistem kilitlenebilir. Makro genişletme bu RET'i kendiliğinden eklemez.
9.1.5.2Kod izleme: PUTC 'A' ve PUTC 'B'
Açılmış kod iki 8 baytlık kopya ve bir RET'tir; toplam 17 bayt. emu8086 yükleme anında CX'e program boyutunu yazdığı için izlemenin başında CX = 0011h'dir. Bu değer program boyunca değişmez, çünkü PUTC CX'e dokunmaz. AX ise başlangıçta 0000h'dir. Her kopyanın makine kodu aynıdır, yalnız karakter baytı farklıdır: 50 B0 41 B4 0E CD 10 58 ve 50 B0 42 B4 0E CD 10 58.
| Adım | Adres | Komut | AX | CX | SP | [SS:SP] | Ekran | Açıklama |
|---|---|---|---|---|---|---|---|---|
| 1 | 0100h | push axPUTC açılımı | 0000h | 0011h | FFFCh | 0000h | – | çağıranın AX'i (0000h) yığına; SP = FFFCh |
| 2 | 0101h | mov al, 'A'PUTC açılımı | 0041h | 0011h | FFFCh | 0000h | – | AL ← 41h |
| 3 | 0103h | mov ah, 0ehPUTC açılımı | 0E41h | 0011h | FFFCh | 0000h | – | AX = 0E41h |
| 4 | 0105h | int 10hPUTC açılımı | 0E41h | 0011h | FFFCh | 0000h | A | ekrana A; imleç bir sağa |
| 5 | 0107h | pop axPUTC açılımı | 0000h | 0011h | FFFEh | 0000h | A | AX eski değerine döndü; SP = FFFEh |
| 6 | 0108h | push axPUTC açılımı | 0000h | 0011h | FFFCh | 0000h | A | 2. kopya: AX yine saklanır |
| 7 | 0109h | mov al, 'B'PUTC açılımı | 0042h | 0011h | FFFCh | 0000h | A | AL ← 42h |
| 8 | 010Bh | mov ah, 0ehPUTC açılımı | 0E42h | 0011h | FFFCh | 0000h | A | AX = 0E42h |
| 9 | 010Dh | int 10hPUTC açılımı | 0E42h | 0011h | FFFCh | 0000h | AB | ekranda AB |
| 10 | 010Fh | pop axPUTC açılımı | 0000h | 0011h | FFFEh | 0000h | AB | AX = 0000h: çağıranın AX'i bozulmadı |
| 11 | 0110h | RETana program | 0000h | 0011h | 0000h | – | AB | program biter |
push ax'ten sonra yığın: çağıranın AX'i (0000h) FFFCh adresine saklanmış, SP = FFFCh olmuştur.Tablo 9.1.14'de birinci adım AX'i yığına iter. SP 2 azalarak FFFCh olur ve SS:FFFCh'deki sözcüğe 0000h yazılır (Şekil 9.1.10). İkinci ve üçüncü adımlar AL'ye 'A' (41h), AH'ye 0Eh yükler; AX = 0E41h olur. Dördüncü adımda INT 10h ekrana A yazar ve imleci ilerletir. INT komutu kendi içinde FLAGS, CS ve IP'yi yığına iter, BIOS yordamına gider ve IRET ile döner; bu iç ayrıntı izlemede tek adım olarak görünür. Beşinci adımdaki POP AX yığındaki 0000h'yi AX'e alır ve SP'yi FFFEh'ye geri getirir. İkinci kopya aynı yolu 'B' ile izler ve ekranda AB oluşur. Son durumda AX = 0000h'dir: makro çağıranın AX'ini bozmamıştır.
Bu izlemede yığın her kopyada bir sözcük büyüyüp küçülür. PUSH ile POP aynı makronun içinde eşleştiği sürece yığın dengelidir: makroya girildiğindeki SP, makrodan çıkıldığında aynıdır. Bu denge bozulursa, örneğin gövdede bir koşullu atlama POP'u atlarsa, yığında fazladan bir sözcük kalır. Ana programın RET'i bu sözcüğü dönüş adresi sanır ve program yanlış bir yere gider.
9.1.5.3Yazmaç saklama makroları
PUTC'deki push ax / pop ax kalıbı, büyük makrolarda dört yazmaca çıkar. Bu kalıp da bir makro olabilir:
SAKLA MACRO
PUSH AX
PUSH BX
PUSH CX
PUSH DX
ENDM
GERI_AL MACRO
POP DX
POP CX
POP BX
POP AX
ENDMGERI_AL, SAKLA'nın tersi sırasıyla çeker. Yığın son giren ilk çıkar (LIFO) ilkesiyle çalışır: en son itilen DX, ilk çekilen olmalıdır. AX = 1111h, BX = 2222h, CX = 3333h, DX = 4444h iken SAKLA kullanılıp yazmaçlar sıfırlansın. GERI_AL'dan sonra dört yazmaç da eski değerine döner. Çekme sırası itme sırasıyla aynı yazılırsa (POP AX, POP BX, POP CX, POP DX) AX = 4444h, BX = 3333h, CX = 2222h, DX = 1111h olur: AX ile DX, BX ile CX yer değiştirir. Bu hata derleme anında yakalanmaz, çünkü her satır kendi başına geçerlidir.
Her SAKLA ve GERI_AL çifti 8 bayt ekler; her PUSH ve POP tek bayttır. Süre olarak 8086'da PUSH r16 11 tur, POP r16 8 tur sürer: 4 × 11 + 4 × 8 = 76 tur. Bu süre içinde sekiz yığın erişimi, yani sekiz yol döngüsü yapılır. Kısa bir makroda bu süre gövdenin kendisini aşabilir; bu yüzden makro yalnız gerçekten değiştirdiği yazmaçları saklamalıdır.
İki makro aynı gövdede birlikte kullanılmalıdır. Arada koşullu bir çıkış (örneğin bir hata durumunda gövdenin sonuna atlama) varsa bu çıkış da GERI_AL'dan geçmelidir; yoksa yığın dengesiz kalır. 80186 ve sonraki işlemcilerde tek komutluk PUSHA ve POPA bütün genel amaçlı yazmaçları saklar ve geri yükler. 8086'da bu komutlar yoktur; bu yüzden aynı iş makroyla yazılır.
9.1.5.4İç içe makrolar
Bir makronun gövdesi başka bir makronun çağrısını içerebilir. Aşağıdaki PRINT2 iki karakter yazmak için PUTC'yi iki kez kullanır:
PRINT2 MACRO c1, c2
PUTC c1
PUTC c2
ENDM
PRINT2 'O', 'K'Assembler genişletmeyi iki düzeyde yapar. Önce dıştaki makroyu açar: PRINT2 'O', 'K' satırı PUTC 'O' ve PUTC 'K' satırlarına dönüşür. Bu iki satır da birer makro çağrısıdır; assembler onları da açar. Sonuçta tek satırlık çağrı on komuta ve 16 bayta dönüşür (Şekil 9.1.11). Ekranda OK görünür.
İç içe kullanımda tanım sırası yine önemlidir. PUTC, PRINT2'nin kullanıldığı satırdan önce tanımlanmış olmalıdır. Bir makronun kendini çağırması (özyineleme) ise bir sınır durumudur. Prosedürde özyinelemeyi yığın sınırlar ve bir koşul durdurur. Makroda durdurma koşulu çalışma anında değil, derleme anında olmalıdır; aksi hâlde genişletme hiç bitmez. Koşullu derleme direktifleri bu tür bir durdurmayı mümkün kılar.
9.1.6POWER makrosu
9.1.6.1Üs alma: MUL ve LOOP
POWER makrosu bir tabanın üssünü hesaplar: r = be. Yöntem en basit olanıdır. Geçici sonuç 1 ile başlatılır ve e kez tabanla çarpılır:
POWER macro b, e, r
mov ax, 1 ; geçici sonuç = 1
mov cx, e ; üs → CX
mov bx, b ; taban → BX
powerloop:
mul bx ; DX:AX = AX * BX
loop powerloop ; e kez tekrarla
mov r, ax ; sonucu yaz
endm
ORG 100h
start:
POWER base, exponent, result
ret
base dw 2 ; taban
exponent dw 3 ; üs
result dw ? ; sonuçMakro iki önemli komut kullanır. MUL BX işaretsiz çarpmadır: AX'i BX ile çarpar ve 32 bitlik sonucu DX:AX yazmaç çiftine yazar. Yüksek 16 bit DX'e, düşük 16 bit AX'e gider. Bu yüzden MUL, işlenen olarak adı geçmese de DX'i her zaman değiştirir. Sonucun yüksek yarısı sıfır değilse CF ve OF 1 olur; sıfırsa ikisi de 0 olur. SF, ZF, AF ve PF ise MUL'dan sonra tanımsızdır.
LOOP etiket, CX'i 1 azaltır ve CX sıfırdan farklıysa etikete atlar. Bayrakları değiştirmez. CX burada döngü sayacıdır; makro ona üssü yükler. 8086'da LOOP atladığında 17, atlamadığında 5 tur sürer. 16 bitlik MUL ise işlenenlerin değerine göre 118 ile 133 tur arasında sürer; bu, komut kümesindeki en yavaş komutlardan biridir.
Açılımın boyutu da hesaplanabilir. mov ax, 1 3 bayt, mov cx, exponent ve mov bx, base bellek işlenenli genel biçimle 4'er bayt, mul bx 2 bayt, loop 2 bayt, mov result, ax AX'e özel kısa biçimle 3 bayttır. Toplam 18 bayttır; RET ile 19 bayt (13h) eder. Değişkenler kodun hemen arkasından başlar: base 0113h'de, exponent 0115h'de, result 0117h'dedir.
9.1.6.2Kod izleme: POWER 2, 3
Açılmış kod şudur:
ORG 100h
mov ax, 1 ; POWER açılımı
mov cx, exponent
mov bx, base
powerloop:
mul bx
loop powerloop
mov result, ax
ret
base dw 2 ; 0113h
exponent dw 3 ; 0115h
result dw ? ; 0117h| Adım | Adres | Komut | AX | BX | CX | DX | CF | result | Açıklama |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 0100h | mov ax, 1POWER açılımı | 0001h | 0000h | 0000h | 0000h | 0 | 0000h | geçici sonuç 1 |
| 2 | 0103h | mov cx, exponentPOWER açılımı | 0001h | 0000h | 0003h | 0000h | 0 | 0000h | CX ← [0115h] = 3 |
| 3 | 0107h | mov bx, basePOWER açılımı | 0001h | 0002h | 0003h | 0000h | 0 | 0000h | BX ← [0113h] = 2 |
| 4 | 010Bh | mul bxPOWER açılımı | 0002h | 0002h | 0003h | 0000h | 0 | 0000h | 1. tur: DX:AX = 1 × 2 |
| 5 | 010Dh | loop powerloopPOWER açılımı | 0002h | 0002h | 0002h | 0000h | 0 | 0000h | CX = 2 ≠ 0: powerloop |
| 6 | 010Bh | mul bxPOWER açılımı | 0004h | 0002h | 0002h | 0000h | 0 | 0000h | 2. tur: 2 × 2 |
| 7 | 010Dh | loop powerloopPOWER açılımı | 0004h | 0002h | 0001h | 0000h | 0 | 0000h | CX = 1 |
| 8 | 010Bh | mul bxPOWER açılımı | 0008h | 0002h | 0001h | 0000h | 0 | 0000h | 3. tur: 4 × 2 |
| 9 | 010Dh | loop powerloopPOWER açılımı | 0008h | 0002h | 0000h | 0000h | 0 | 0000h | CX = 0: döngüden çık |
| 10 | 010Fh | mov result, axPOWER açılımı | 0008h | 0002h | 0000h | 0000h | 0 | 0008h | [0117h] ← 0008h |
| 11 | 0112h | retana program | 0008h | 0002h | 0000h | 0000h | 0 | 0008h | program biter |
Tablo 9.1.15 döngünün üç turunu gösterir. Birinci turda MUL, 1 × 2 = 2 hesaplar: DX:AX = 0000:0002h. Yüksek yarı sıfır olduğundan CF = 0'dır. LOOP, CX'i 3'ten 2'ye indirir ve powerloop'a döner. İkinci turda AX = 4, üçüncü turda AX = 8 olur. Üçüncü LOOP CX'i 0 yapar ve atlamaz. mov result, ax 0117h'deki sözcüğe 0008h yazar; 23 = 8.
Tabloda DX'in her MUL'dan sonra 0000h olduğu görülür. Sonuç 16 bite sığdığı sürece bu böyledir. Sonuç 16 biti aşsaydı, örneğin 216 hesaplansaydı, son MUL'dan sonra DX = 0001h, AX = 0000h ve CF = OF = 1 olurdu. Makro yalnız AX'i result'a yazdığı için result = 0 olurdu; doğru sonucun yüksek kısmı DX'te kalır ve kaybolur.
9.1.6.3Dört tuzak
POWER makrosu küçük ama dört ayrı hata barındırır. Her biri makro yazarken sık görülen bir sorun türünü temsil eder.
1. Üs sıfır. Matematikte b0 = 1'dir; makronun AX = 1 ile başlaması bu sonucu verecek gibi görünür. Ama MUL en az bir kez çalışır, çünkü döngünün koşulu sonda denetlenir. Daha kötüsü, LOOP önce CX'i azaltır, sonra sıfırla karşılaştırır. CX = 0 iken azaltma CX'i FFFFh yapar; bu sıfır değildir ve döngü sürer. Döngü ancak CX yeniden 0 olduğunda, yani 65 536 tur sonra biter. base = 2 için 216'dan sonra AX'in düşük 16 biti sıfırdır ve öyle kalır; result = 0000h olur. base = 1 için result = 1 çıkar. Bu tesadüfen doğrudur; 65 536 gereksiz MUL yine de çalışmıştır. Her MUL yaklaşık 120 tur sürdüğünden bu boşa giden zaman 4,77 MHz'de bir buçuk saniyeyi aşar.
2. Etiket. powerloop LOCAL ile bildirilmemiştir. Makro ikinci kez kullanıldığında etiket iki kez tanımlanır ve Duplicate declaration hatası alınır.
3. Yazmaç bozulması. Makro AX'e sonucu koyar; bu beklenen bir değişikliktir. Ama BX'e tabanı, CX'e sayacı yükler ve MUL yüzünden DX'i de değiştirir. Çağıranın bu üç yazmaçtaki değerleri kaybolur.
4. Taşma. Sonuç 65 535'i aşarsa yüksek kısım DX'te kalır ve yazılmaz. Makro, CF ile bu durumu çağırana bildirmez de; son MUL'un CF değeri makrodan sonra durur ama sonraki herhangi bir komut onu silebilir.
Düzeltilmiş sürüm ilk üç sorunu giderir:
POWER MACRO b, e, r
LOCAL p_loop, p_end
PUSH BX
PUSH CX
PUSH DX
MOV AX, 1
MOV CX, e
MOV BX, b
JCXZ p_end ; e = 0 → 1
p_loop: MUL BX
LOOP p_loop
p_end: MOV r, AX
POP DX
POP CX
POP BX
ENDMJCXZ (CX sıfırsa atla), LOOP'tan önce sayacı denetler. CX = 0 ise döngünün tamamını atlar ve AX'teki 1 doğrudan sonuca yazılır. LOCAL iki etiketi her açılımda benzersiz yapar. PUSH ve POP, BX, CX ve DX'i korur; makro artık yalnız AX'i değiştirir. Taşma sorunu ise bu sürümde de durur. Onu çözmek için ya sonuç 32 bit (DX:AX) olarak yazılmalı ya da taşma CF ile çağırana bildirilmelidir.
LOOP'lu bir döngüyü sayaç sıfır olabilecekken başlatmak. LOOP önce azaltır, sonra denetler; CX = 0 ile 65 536 kez döner. Sayacı bir değişkenden ya da parametreden alan her döngünün önüne JCXZ konur.
9.1.6.4Kod izleme: düzeltilmiş POWER
Düzeltilmiş makronun POWER taban, us, sonuc açılımı, taban = 7 ve us = 2 için izlenir. Yazmaç korumanın etkisini görmek için çağıranın yazmaçlarında BX = 0AAAh, CX = 0BBBh, DX = 0CCCh olsun:
org 100h
push bx ; POWER açılımı
push cx
push dx
mov ax, 1
mov cx, us
mov bx, taban
jcxz ??0001 ; us = 0 → 1
??0000: mul bx
loop ??0000
??0001: mov sonuc, ax
pop dx
pop cx
pop bx
ret
taban dw 7
us dw 2
sonuc dw ?Açılım 26 bayttır (011Ah'ye kadar); RET 011Ah'de, taban 011Bh'de, us 011Dh'de, sonuc 011Fh'dedir. LOCAL adları p_loop için ??0000, p_end için ??0001'dir.
| Adım | Adres | Komut | AX | BX | CX | DX | SP | sonuc | Açıklama |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 0100h | PUSH BXPOWER açılımı | 0000h | 0AAAh | 0BBBh | 0CCCh | FFFCh | 0000h | BX saklandı |
| 2 | 0101h | PUSH CXPOWER açılımı | 0000h | 0AAAh | 0BBBh | 0CCCh | FFFAh | 0000h | CX saklandı |
| 3 | 0102h | PUSH DXPOWER açılımı | 0000h | 0AAAh | 0BBBh | 0CCCh | FFF8h | 0000h | DX saklandı; SP = FFF8h |
| 4 | 0103h | MOV AX, 1POWER açılımı | 0001h | 0AAAh | 0BBBh | 0CCCh | FFF8h | 0000h | geçici sonuç 1 |
| 5 | 0106h | MOV CX, usPOWER açılımı | 0001h | 0AAAh | 0002h | 0CCCh | FFF8h | 0000h | CX ← us = 2 |
| 6 | 010Ah | MOV BX, tabanPOWER açılımı | 0001h | 0007h | 0002h | 0CCCh | FFF8h | 0000h | BX ← taban = 7 |
| 7 | 010Eh | JCXZ ??0001POWER açılımı | 0001h | 0007h | 0002h | 0CCCh | FFF8h | 0000h | CX ≠ 0: atlama yok |
| 8 | 0110h | ??0000: MUL BXPOWER açılımı | 0007h | 0007h | 0002h | 0000h | FFF8h | 0000h | 1 × 7; MUL DX'i de yazar |
| 9 | 0112h | LOOP ??0000POWER açılımı | 0007h | 0007h | 0001h | 0000h | FFF8h | 0000h | CX = 1 |
| 10 | 0110h | ??0000: MUL BXPOWER açılımı | 0031h | 0007h | 0001h | 0000h | FFF8h | 0000h | 7 × 7 = 49 = 0031h |
| 11 | 0112h | LOOP ??0000POWER açılımı | 0031h | 0007h | 0000h | 0000h | FFF8h | 0000h | CX = 0: döngü biter |
| 12 | 0114h | ??0001: MOV sonuc, AXPOWER açılımı | 0031h | 0007h | 0000h | 0000h | FFF8h | 0031h | [011Fh] ← 0031h |
| 13 | 0117h | POP DXPOWER açılımı | 0031h | 0007h | 0000h | 0CCCh | FFFAh | 0031h | DX = 0CCCh |
| 14 | 0118h | POP CXPOWER açılımı | 0031h | 0007h | 0BBBh | 0CCCh | FFFCh | 0031h | CX = 0BBBh |
| 15 | 0119h | POP BXPOWER açılımı | 0031h | 0AAAh | 0BBBh | 0CCCh | FFFEh | 0031h | BX = 0AAAh; SP = FFFEh |
| 16 | 011Ah | retana program | 0031h | 0AAAh | 0BBBh | 0CCCh | 0000h | 0031h | program biter |
Tablo 9.1.16'de ilk üç adım çağıranın yazmaçlarını yığına saklar (Şekil 9.1.12). Yığın aşağı doğru büyür: 0AAAh FFFCh'ye, 0BBBh FFFAh'ya, 0CCCh FFF8h'ye yazılır. Sonraki üç adımda yazmaçlar makronun kullanımına geçer: AX = 1, CX = 2, BX = 7. JCXZ, CX ≠ 0 olduğu için atlamaz. Birinci MUL 1 × 7 = 7 verir ve DX'i 0000h yapar. İkinci MUL 7 × 7 = 49 = 0031h verir. LOOP CX'i 0'a indirince döngü biter ve sonuc'a 0031h yazılır.
Son üç adım yığını ters sırada boşaltır. POP DX 0CCCh'yi, POP CX 0BBBh'yi, POP BX 0AAAh'yı geri getirir; SP yeniden FFFEh olur. Makrodan çıkıldığında BX, CX ve DX'in değerleri girişteki değerlerin aynısıdır. Yalnız AX değişmiştir ve AX zaten sonucu taşır. us = 0 ile çalıştırılsaydı JCXZ doğrudan ??0001'e atlar, sonuc = 1 olur ve tek bir MUL bile çalışmazdı.
9.1.7Makro kütüphaneleri
9.1.7.1INCLUDE ve emu8086.inc
Sık kullanılan makrolar her programa yeniden yazılmaz; ayrı bir dosyada toplanır. Bu dosyaya makro kütüphanesi (library) denir. INCLUDE direktifi, derleme anında kütüphane dosyasının metnini kaynağa, direktifin bulunduğu yere ekler. Assembler için bu, makroların programın başına elle yazılmasından farksızdır. Kullanılmayan makrolar kod üretmediği için büyük bir kütüphaneyi eklemek programı büyütmez; yalnız kullanılan makrolar açılır.
include 'emu8086.inc'
ORG 100h
GOTOXY 10, 5
PRINT 'Merhaba'
PUTC '!'
RETemu8086 ile gelen emu8086.inc dosyası bu düzenin hazır bir örneğidir. İçinde ekrana karakter ve metin yazan (PUTC, PRINT, PRINTN), imleci taşıyan (GOTOXY) ve gizleyen (CURSOROFF) makrolar bulunur. Sayı okuma ve yazdırma gibi uzun işler için kütüphane prosedür de sunar. Bu prosedürler DEFINE_SCAN_NUM gibi özel makrolarla programa bir kez eklenir ve CALL ile çağrılır. Böylece kütüphane, makro ile prosedürün birlikte kullanıldığı düzeni izler: kısa işler makro, uzun işler tek kopya prosedürdür. Kütüphanenin makroları ve prosedürleri ayrıntısıyla emu8086.inc kütüphanesini anlatan notta ele alınır.
9.1.7.2Kod izleme: PRINT 'OK' açılımı
Kütüphanedeki PRINT makrosu, buraya kadarki teknikleri bir arada kullanır: yazmaç koruma, LOCAL etiketler ve makro içinde tanımlanan veri. PRINT 'OK' çağrısı şu koda açılır (LOCAL adları assembler'ın ürettiği biçimde):
org 100h
push ax ; PRINT 'OK'
push si
jmp ??0003 ; veriyi atla
??0001 db 'OK', 0
??0003: lea si, ??0001
??0000: mov al, cs:[si]
cmp al, 0
jz ??0002
inc si
mov ah, 0Eh
int 10h
jmp ??0000
??0002: pop si
pop ax
retDizgi 'OK' ve sonundaki 0 baytı, makronun açıldığı yere, kodun içine gömülür: 0104h'den başlayarak 4Fh, 4Bh, 00h. İşlemci kod ile veriyi ayırt etmediği için bu üç baytı komut sanabilir. Bunu önlemek için makro önce JMP ile dizginin üzerinden atlar. Dizginin adı da LOCAL'dır (??0001); aynı programda ikinci bir PRINT kendi dizgisini başka bir adla tanımlar.
Dizgi kod kesiminde durduğu için onu okuyan komut CS: kesim önekini kullanır: MOV AL, CS:[SI]. Önek olmasaydı okuma varsayılan kesim olan DS'den yapılırdı. Bir .COM programında CS = DS olduğundan sonuç aynı çıkardı, ama makro DS'nin başka bir kesimi gösterdiği .EXE programlarında da çalışmak zorundadır. Önek, makine kodunda 2Eh baytıdır ve komutu bir bayt uzatır.
LEA SI, ??0001 komutu emu8086'da MOV SI, OFFSET ??0001 ile aynı 3 baytlık koda derlenir: BE 04 01. Gerçek MASM'da doğrudan adresli LEA 4 bayt (8D 36 + adres) tutar ve açılım bir bayt uzun olurdu. Açılımın toplam boyutu 27 bayttır (0100h–011Ah); bunun 3 baytı veridir. Genel olarak n karakterlik bir metin için PRINT açılımı 25 + n bayt tutar.
| Adım | Adres | Komut | AL | SI | SP | ZF | Ekran | Açıklama |
|---|---|---|---|---|---|---|---|---|
| 1 | 0100h | push axPRINT 'OK' açılımı | 00h | 0000h | FFFCh | 0 | – | AX saklandı |
| 2 | 0101h | push siPRINT 'OK' açılımı | 00h | 0000h | FFFAh | 0 | – | SI saklandı; SP = FFFAh |
| 3 | 0102h | jmp ??0003PRINT 'OK' açılımı | 00h | 0000h | FFFAh | 0 | – | dizginin baytlarının üzerinden atla |
| 4 | 0107h | ??0003: lea si, ??0001PRINT 'OK' açılımı | 00h | 0104h | FFFAh | 0 | – | SI ← 0104h |
| 5 | 010Ah | ??0000: mov al, cs:[si]PRINT 'OK' açılımı | 4Fh | 0104h | FFFAh | 0 | – | AL ← CS:[0104h] = 'O' |
| 6 | 010Dh | cmp al, 0PRINT 'OK' açılımı | 4Fh | 0104h | FFFAh | 0 | – | 4Fh ≠ 0: ZF = 0 |
| 7 | 010Fh | jz ??0002PRINT 'OK' açılımı | 4Fh | 0104h | FFFAh | 0 | – | atlamaz |
| 8 | 0111h | inc siPRINT 'OK' açılımı | 4Fh | 0105h | FFFAh | 0 | – | SI = 0105h |
| 9 | 0112h | mov ah, 0EhPRINT 'OK' açılımı | 4Fh | 0105h | FFFAh | 0 | – | |
| 10 | 0114h | int 10hPRINT 'OK' açılımı | 4Fh | 0105h | FFFAh | 0 | O | ekrana O |
| 11 | 0116h | jmp ??0000PRINT 'OK' açılımı | 4Fh | 0105h | FFFAh | 0 | O | döngü başına |
| 12 | 010Ah | ??0000: mov al, cs:[si]PRINT 'OK' açılımı | 4Bh | 0105h | FFFAh | 0 | O | AL = 'K' |
| 15 | 0111h | inc siPRINT 'OK' açılımı | 4Bh | 0106h | FFFAh | 0 | O | SI = 0106h |
| 17 | 0114h | int 10hPRINT 'OK' açılımı | 4Bh | 0106h | FFFAh | 0 | OK | ekranda OK |
| 19 | 010Ah | ??0000: mov al, cs:[si]PRINT 'OK' açılımı | 00h | 0106h | FFFAh | 0 | OK | AL = 00h |
| 20 | 010Dh | cmp al, 0PRINT 'OK' açılımı | 00h | 0106h | FFFAh | 1 | OK | sonlandırıcı: ZF = 1 |
| 21 | 010Fh | jz ??0002PRINT 'OK' açılımı | 00h | 0106h | FFFAh | 1 | OK | çıkışa atla |
| 22 | 0118h | ??0002: pop siPRINT 'OK' açılımı | 00h | 0000h | FFFCh | 1 | OK | SI = 0000h |
| 23 | 0119h | pop axPRINT 'OK' açılımı | 00h | 0000h | FFFEh | 1 | OK | AX geri; SP = FFFEh |
| 24 | 011Ah | retana program | 00h | 0000h | 0000h | 1 | OK | program biter |
Tablo 9.1.17 döngünün üç turunu gösterir; tekrar eden MOV AH ve INT adımları kısaltılmıştır. Birinci turda AL = 4Fh ('O') okunur. CMP AL, 0 ZF = 0 verir ve JZ atlamaz. SI bir ilerler, INT 10h 'O'yu yazar ve JMP döngü başına döner. İkinci tur 'K'yı yazar. Üçüncü turda AL = 00h okunur; CMP ZF = 1 verir ve JZ çıkışa (??0002) atlar. Sondaki 0 baytı, dizginin bittiğini gösteren sonlandırıcıdır. İki POP, SI'yı ve AX'i eski değerlerine döndürür; SP yeniden FFFEh olur. Ekranda OK yazmıştır ve çağıranın yazmaçları bozulmamıştır.
Bu açılım makronun gücünü ve maliyetini birlikte gösterir. Kaynakta tek satır olan PRINT 'OK', 27 bayt üretmiştir. Program boyunca yirmi farklı metin PRINT ile yazılırsa her biri kendi döngüsünü taşır. Aynı iş, dizgileri ayrı bir yerde tutan ve tek bir yazdırma prosedürü çağıran bir düzenle çok daha az yer tutar. Kütüphanenin PRINT_STRING gibi prosedürleri tam bu amaçla vardır.
9.1.8Makro işleçleri
Makro genişletme metin işlemi olduğuna göre, metni parçalamak ve birleştirmek için özel işaretlere ihtiyaç vardır. MASM ve TASM bu amaçla beş makro işleci tanımlar. Bu işleçler yalnız makro tanımı ve çağrısı içinde anlam taşır.
| İşleç | Görevi | Örnek ve açılım |
|---|---|---|
& | parametreyi bitişik metne ekler | SIFIRLA MACRO r · XOR r&X, r&X → SIFIRLA B: XOR BX, BX |
< > | virgül ya da boşluk içeren metni tek parametre yapar | YAZ <'A', 'B'> → tek parametre: 'A', 'B' |
! | sonraki karakteri sıradan karakter yapar | <5 !> 3> → metin: 5 > 3 |
% | ifadeyi hesaplar, sonucu metin olarak koyar | GOSTER %(4*8) → parametre 32 |
;; | makro açıklaması; açılıma kopyalanmaz | ;; yalnız tanımda görünür |
& (birleştirme) işleci en sık kullanılanıdır. Assembler gövdede parametre adlarını bütün sözcükler olarak arar. XOR rX, rX yazılsaydı "rX" tek bir sözcük sayılır ve r parametresiyle eşleşmezdi; açılım XOR rX, rX olarak kalır ve bilinmeyen bir ad hatası verirdi. r&X yazımı, r'nin bir parametre adı olduğunu ve ardından gelen X'in ona yapıştırılacağını belirtir. SIFIRLA B çağrısında r = B olur ve açılım XOR BX, BX olur. Bir yazmacı kendisiyle XOR'lamak onu sıfırlar ve 2 baytlık, 3 turluk bir komuttur; MOV BX, 0 ise 3 bayt tutar ve 4 tur sürer.
Açı ayraçları < >, virgül içeren bir metni tek parametre olarak geçirmeyi sağlar. Ayraçlar olmadan YAZ 'A', 'B' iki ayrı parametre sayılırdı. ! işleci, kendisinden sonraki karakterin özel anlamını kaldırır; <5 !> 3> içindeki ilk > ayracı kapatmaz, metnin bir parçası olur. % işleci ise parametreyi metin olarak değil, değer olarak geçirir: %(4*8) önce 32'ye hesaplanır, makroya "32" metni gider. Bu, işlem önceliği tuzağına karşı paranteze alternatif bir korumadır.
Son işleç açıklamalarla ilgilidir. Tek ; ile yazılan açıklama gövdenin bir parçasıdır ve her açılımda liste dosyasına yeniden yazılır. Çift ;; ile yazılan açıklama yalnız tanımda kalır, açılıma kopyalanmaz. Yüz kez kullanılan bir makroda bu, liste dosyasını yüzlerce gereksiz satırdan kurtarır.
9.1.9Tekrar blokları
9.1.9.1REPT, IRP ve IRPC
Bazen bir kod parçasının, makro tanımlamaya gerek kalmadan, olduğu yerde birkaç kez yazılması istenir. Tekrar blokları bu işi yapar.
Tekrar bloğu (repeat block), adı olmayan ve tanımlandığı yerde hemen açılan bir makrodur. REPT n gövdeyi n kez, IRP p, <liste> listedeki her öğe için bir kez (p o öğeyle değiştirilerek), IRPC p, metin metindeki her karakter için bir kez yazar. Blok ENDM ile biter. MASM 6'da IRP ve IRPC, FOR ve FORC adlarıyla da yazılır.
REPT 3
SHL AX, 1
ENDM ; → SHL AX, 1 · SHL AX, 1 · SHL AX, 1
IRP r, <AX,BX,CX>
PUSH r
ENDM ; → PUSH AX · PUSH BX · PUSH CX
IRPC c, OK
PUTC '&c'
ENDM ; → PUTC 'O' · PUTC 'K'REPT örneği AX'i üç kez bir bit sola kaydırır, yani 8 ile çarpar: AX = 5 iken sonuç 40 (0028h) olur. IRP örneği üç yazmacı yığına iter; SAKLA makrosunun kısa bir yazımıdır. IRPC örneğinde c, sırayla O ve K karakterleri olur. Tırnak içinde c'nin parametre olarak tanınması için &c yazılır; tırnak içindeki metin aksi hâlde olduğu gibi kalırdı. Açılımdaki iki PUTC de birer makro olduğu için iç içe genişletme olur ve ekranda OK yazılır.
Tekrar bloğu bir döngü değildir. Döngü sayacına gerek kalmaz ama kod büyür: REPT 100 gövdeyi yüz kez kopyalar. REPT örneğinin üç SHL'si 6 bayt tutar ve 3 × 2 = 6 turda çalışır. Aynı kaydırma MOV CL, 3 ve SHL AX, CL ile 4 baytta yazılabilir. Ama 8086'da CL'li kaydırma her bit için 4 tur ekler ve iş 4 + (8 + 4 × 3) = 24 tur sürer. Kısa tekrarlarda REPT hem hızlı hem de makul boyuttadır; REPT 100 ise 200 bayt tutar ve aynı iş sayaçlı bir LOOP ile tek kopyada yapılır.
Tekrar bloklarının en yaygın kullanımı derleme anında tablo üretmektir. Bunun için değeri değiştirilebilen bir assembler sembolü gerekir. EQU ile tanımlanan bir sabit yeniden tanımlanamaz. = ile tanımlanan sembol ise her atamada yeni bir değer alabilir. Aşağıdaki blok 0'dan 7'ye kadar sayıların karelerini üretir:
n = 0
kare LABEL BYTE
REPT 8
DB n*n
n = n + 1
ENDMkare LABEL BYTE, o anki adrese bayt türünde bir ad verir ama yer ayırmaz. REPT gövdesi sekiz kez açılır. Her açılımda DB n*n o anki n'nin karesini bir bayt olarak yazar ve n = n + 1 sembolü bir artırır. Sonuçta bellekte 0, 1, 4, 9, 16, 25, 36, 49 baytları art arda durur. n bir değişken değildir; bellekte yeri yoktur. Yalnız derleme anında, assembler'ın tablosunda yaşayan bir sayıdır. Aynı yöntemle maskeler, 2'nin kuvvetleri ya da önceden hesaplanmış sinüs değerleri gibi tablolar üretilir.
9.1.9.2Kod izleme: REPT ile kare tablosu
Üretilen tablo, XLAT komutuyla bir sayının karesini çarpma yapmadan bulmak için kullanılabilir:
org 100h
lea bx, kare
mov al, 5
xlat ; AL = kare[5]
mov dl, al
mov al, 7
xlat ; AL = kare[7]
add al, dl
ret
kare db 0, 1, 4, 9, 16, 25, 36, 49 ; REPT bloğunun ürettiği baytlarXLAT (tablodan çevir), AL'yi BX'in gösterdiği tablonun indisi olarak kullanır: AL ← DS:[BX + AL]. Tablonun adresi önce BX'e yüklenmelidir. Kodun boyutu 3 + 2 + 1 + 2 + 2 + 1 + 2 + 1 = 14 bayttır; LEA emu8086'da 3 bayt tuttuğu için tablo 0100h + 0Eh = 010Eh'de başlar.
| Adım | Adres | Komut | BX | AL | DL | Açıklama |
|---|---|---|---|---|---|---|
| 1 | 0100h | lea bx, kareana program | 010Eh | 00h | 00h | BX ← 010Eh (kare'nin bağıl konumu) |
| 2 | 0103h | mov al, 5ana program | 010Eh | 05h | 00h | aranan sayı ve indis 5 |
| 3 | 0105h | xlatana program | 010Eh | 19h | 00h | AL ← [BX + AL] = [0113h] = 25 |
| 4 | 0106h | mov dl, alana program | 010Eh | 19h | 19h | ilk sonuç DL'de |
| 5 | 0108h | mov al, 7ana program | 010Eh | 07h | 19h | indis 7 |
| 6 | 010Ah | xlatana program | 010Eh | 31h | 19h | AL ← [0115h] = 49 |
| 7 | 010Bh | add al, dlana program | 010Eh | 4Ah | 19h | 25 + 49 = 74 = 4Ah |
| 8 | 010Dh | retana program | 010Eh | 4Ah | 19h | program biter |
Tablo 9.1.19'de birinci XLAT, 010Eh + 5 = 0113h adresindeki baytı okur: 25 = 19h (Şekil 9.1.14). Bu değer DL'ye saklanır. İkinci XLAT 010Eh + 7 = 0115h adresinden 49 = 31h okur. ADD AL, DL ikisini toplar: 25 + 49 = 74 = 4Ah, yani 5² + 7². Program boyunca hiçbir çarpma komutu çalışmamıştır.
Hız farkı belirgindir. XLAT 11 turda biter. 8086'da 8 bitlik bir MUL ise 70–77 tur sürer. Tablonun bedeli bellektir: sekiz bayt. Bu, bellek ile zaman arasındaki klasik bir takastır ve REPT bu takası kaynakta tek bir satır gibi kısa tutar. Tablo derleme anında üretildiği için çalışma anında onu hesaplamak için de zaman harcanmaz.
9.1.10Koşullu derleme
9.1.10.1IF ailesi
Koşullu derleme, kaynak kodun bir bölümünün derleme anında programa alınıp alınmayacağını seçer. C dilindeki #if ve #ifdef önişlemci direktiflerinin assembly karşılığıdır.
Koşullu derleme (conditional assembly), IF, IFB, ELSE, ENDIF gibi direktiflerle bir kaynak bölümünün derleme anında koda katılıp katılmayacağının seçilmesidir. Koşul derleme anında değerlendirilir; seçilmeyen dal makine koduna hiç girmez.
| Direktif | Dal ne zaman alınır? | Tipik kullanım |
|---|---|---|
IF ifade | ifade ≠ 0 | deneme sürümü, seçenekler |
IFE ifade | ifade = 0 | IF'in tersi |
IFB <p> | parametre boş | isteğe bağlı parametre |
IFNB <p> | parametre dolu | isteğe bağlı parametre |
IFIDN <a>,<b> | iki metin aynı | yazmaç adını denetleme |
IFDIF <a>,<b> | iki metin farklı | yazmaç adını denetleme |
Her koşullu blok ENDIF ile biter ve isteğe bağlı bir ELSE dalı taşıyabilir. Koşul, çalışma anındaki bir bayrağa ya da yazmaca bakmaz. Assembler'ın derleme anında bildiği şeylere bakar: sembollerin değerleri ve makro parametrelerinin metni. Bu yüzden koşullu derleme çalışma anında hiçbir komut, karşılaştırma ya da atlama üretmez.
İlk kullanım alanı programın farklı sürümlerini aynı kaynaktan üretmektir:
HATA_AYIKLA = 1
IF HATA_AYIKLA
PUTC '*' ; yalnız deneme sürümünde
ENDIFHATA_AYIKLA = 1 iken PUTC açılır ve program 8 bayt büyür; ekrana önce bir yıldız yazılır. Sembol 0 yapıldığında bu satırlar hiç derlenmez ve programda iz bırakmaz. Deneme amaçlı ekran çıktıları ya da denetimler böylece kaynaktan silinmeden son sürümden çıkarılır.
İkinci kullanım alanı makroların içidir. IFB ve IFNB (boş, boş değil) isteğe bağlı parametreleri mümkün kılar. Aşağıdaki KAYDIR makrosu bir yazmacı sola kaydırır; kaydırma sayısı verilmezse 1 kabul edilir:
KAYDIR MACRO r, n
IFB <n> ;; n verilmedi mi?
SHL r, 1
ELSE
MOV CL, n
SHL r, CL
ENDIF
ENDM8086'da kaydırma sayısı ya 1'dir ya da CL'de verilir. SHL AX, 3 gibi bir anında sayı 80186 ile gelmiştir. Makro bu kuralı kullanıcıdan gizler: n boşsa tek bitlik kaydırma, doluysa CL'li kaydırma üretir. Parametrenin açı ayraçları içinde yazılması (<n>), boş metnin de bir metin olarak karşılaştırılabilmesini sağlar.
IFIDN ve IFDIF, parametre tuzaklarına karşı bir güvenlik ağı kurar. KAYDIR CL'yi bozar; KAYDIR CX, 2 çağrısı bu yüzden yanlış sonuç verir. MOV CL, 2 CX'in düşük baytını ezer ve kaydırılan değer artık programcının CX'i değildir. Gövdeye IFIDN <r>, <CX> ile başlayan bir blok eklenip bu durumda bir hata iletisi üretilebilir. EXITM direktifi ise açılımı o noktada bitirir; kalan gövde satırları yazılmaz. Koşullu derlemeyle birlikte kullanıldığında özyinelemeli bir makronun durma koşulunu da sağlar.
9.1.10.2Kod izleme: KAYDIR
org 100h
mov ax, 5
mov bx, 3
KAYDIR AX ; → shl ax, 1
KAYDIR BX, 3 ; → mov cl, 3 · shl bx, cl
ret| Adım | Adres | Komut | AX | BX | CL | CF | Açıklama |
|---|---|---|---|---|---|---|---|
| 1 | 0100h | mov ax, 5ana program | 0005h | 0000h | 00h | 0 | AX = 5 |
| 2 | 0103h | mov bx, 3ana program | 0005h | 0003h | 00h | 0 | BX = 3 |
| 3 | 0106h | SHL AX, 1KAYDIR açılımı | 000Ah | 0003h | 00h | 0 | n boş, IFB dalı: AX = 5 × 2 |
| 4 | 0108h | MOV CL, 3KAYDIR açılımı | 000Ah | 0003h | 03h | 0 | n = 3, ELSE dalı: sayı CL'ye |
| 5 | 010Ah | SHL BX, CLKAYDIR açılımı | 000Ah | 0018h | 03h | 0 | 3 × 2³ = 24 = 0018h |
| 6 | 010Ch | retana program | 000Ah | 0018h | 03h | 0 | program biter |
Birinci çağrıda n boştur; assembler IFB dalını seçer ve tek bir SHL AX, 1 yazar (2 bayt, D1 E0). AX = 5 × 2 = 000Ah olur. İkinci çağrıda n = 3'tür; ELSE dalı seçilir ve iki komut yazılır: MOV CL, 3 (B1 03) ve SHL BX, CL (D3 E3), toplam 4 bayt. BX = 3 × 2³ = 24 = 0018h olur. Aynı makro iki farklı uzunlukta kod üretmiştir. Birinci açılım CL'ye dokunmaz; yalnız ikinci açılım CL'yi değiştirir.
İzlemede bir karşılaştırma ya da atlama yoktur. Hangi dalın yazılacağına assembler karar vermiş, işlemci yalnız sonucu yürütmüştür. Aynı seçim çalışma anında yapılsaydı her çağrıda bir CMP ve bir koşullu atlama gerekirdi; hem kod büyür hem de önden getirme kuyruğu boşaltılırdı.
9.1.11Hata ayıklama ve sık yapılan hatalar
Makrolar kaynakta kodu kısaltır ama hata ayıklamayı zorlaştırır, çünkü yürüyen kod kaynakta yazılı değildir. Dört alışkanlık bu zorluğu azaltır.
Açılımı görmek. emu8086'nın öykünücü penceresindeki komut listesi makro adlarını değil, açılmış komutları gösterir; tek adım çalıştırma da bu komutlar üzerinde ilerler. Makro etiketleri LOCAL ile ??0000 gibi adlar aldığı için atlama hedefleri kaynaktaki adlarla görünmez. Hangi kopyanın çalıştığını bu numaralar gösterir.
Liste dosyası. MASM'da .LISTMACROALL açılımın bütün satırlarını liste dosyasına yazar. .LISTMACRO yalnız kod üreten satırları yazar, .NOLISTMACRO açılımı tamamen gizler. Liste dosyası her satırın adresini ve makine kodunu yan yana verdiği için bayt hesabı ve adres denetimi için en güvenilir kaynaktır.
Hata satırını doğru okumak. Gövdedeki bir hata, makronun kullanıldığı her satırda ayrı ayrı bildirilir. Aynı hata iletisi birkaç kez görülüyorsa neden çoğunlukla gövdededir.
Önce elle açmak. Yeni bir makro, gövdesi önce düz kod olarak yazılıp denenerek geliştirilir. Kod bir parametre değeriyle doğru çalıştıktan sonra parametreli makroya dönüştürülür. Sonra farklı türde parametrelerle (yazmaç, değişken, sabit, ifade) ayrı ayrı denenir.
| Hata | Sonuç | Çözüm |
|---|---|---|
| kullanımdan sonra tanımlamak | çağrı bilinmeyen komut sayılır | tanımlar dosyanın başına ya da INCLUDE dosyasına |
| ENDM'i unutmak | dosyanın geri kalanı gövde sayılır; hata çok aşağıda görünür | her MACRO'nun ENDM'ini denetlemek |
| etiketi LOCAL yapmamak | ikinci kullanımda Duplicate declaration | LOCAL gövdenin ilk satırı |
| gövdede RET | RET çağıranın dönüş adresini ya da rastgele bir sözcüğü alır | makroda RET yazmamak |
| gövdenin bozduğu yazmacı parametre vermek | TOPLA AX, BX, CX gibi çağrılarda parametre okunmadan değişir | kullanılan yazmaçları belgelemek, IFIDN ile denetlemek |
| parantezsiz ifade | işlem önceliği değişir | (en)*(boy) |
| makroyu döngü sanmak | REPT 1000 1000 kopya üretir | çalışma anında tekrar için LOOP |
| uzun gövdeyi çok kullanmak | kod boyutu kullanım sayısıyla büyür | işi prosedüre taşımak, makroyu kılıf yapmak |
Tablo 9.1.22 sık görülen sekiz hatayı toplar. Hepsinin ortak kökü aynıdır: makro bir metin kalıbıdır, çağrılan bir birim değildir. Bir makronun ne yapacağını anlamanın en güvenilir yolu, onu açıp ortaya çıkan komutları sıradan bir program gibi okumaktır.
9.1.12Alıştırmalar ve çözümleri
Alıştırma 1. MyMacro AX, BX, CX çağrısının genişletilmiş hâli nedir? Hangi satır gereksizdir?
Çözüm. p1 = AX, p2 = BX, p3 = CX olur. Açılım MOV AX, AX, MOV BX, BX, MOV CX, CX satırlarıdır. Üçü de geçerli komutlardır ve 2'şer bayta derlenir (8B C0, 8B DB, 8B C9). Ama hiçbiri bir yazmacı değiştirmez; MOV bayrakları da etkilemez. Üç satırın üçü de gereksizdir: 6 bayt ve 6 tur boşa gider. AX = 1, BX = 2, CX = 3 ile başlayan bir program makrodan sonra da aynı değerleri taşır. Makro hatasız derlenir ama hiçbir iş yapmaz; assembler bu tür etkisiz açılımlar için uyarı vermez.
Alıştırma 2. İki sözcük değişkenini toplayıp üçüncüsüne yazan TOPLA a, b, c makrosunu yazın.
Çözüm.
TOPLA MACRO a, b, c
MOV AX, a
ADD AX, b
MOV c, AX
ENDM8086'da bellekten belleğe toplama olmadığı için AX ara yazmaçtır. Makro AX'i değiştirir; bu, makronun belgesinde belirtilmelidir. s1 = 1234h, s2 = F00Fh ile TOPLA s1, s2, s3 çağrısı s3 = 0243h ve CF = 1 verir; işaretsiz toplamın 17. biti eldededir.
Alıştırma 3. PUTC makrosu 50 kez kullanılırsa, aynı işi yapan prosedür 50 kez çağrılırsa kod boyutları ne olur?
Çözüm. Makro: 50 × 8 = 400 bayt. Prosedür: gövde 8 bayt + RET 1 bayt = 9 bayt bir kez, artı 50 × 3 = 150 bayt CALL; toplam 159 bayt. Süre açısından prosedür 50 × 27 = 1350 tur ek getirir.
Alıştırma 4. İlk POWER makrosu POWER base, exponent, result ve ikinci bir çağrıyla iki kez kullanıldığında hangi hata alınır? Nasıl düzeltilir?
Çözüm. İkinci açılım powerloop: etiketini yeniden tanımlar; assembler Duplicate declaration (powerloop) hatası verir ve program derlenmez. Düzeltme, makronun ilk satırına LOCAL powerloop eklemektir. Böylece birinci açılımda etiket ??0000, ikincide ??0001 olur ve her LOOP kendi kopyasının etiketine döner.
Alıştırma 5. exponent = 0 iken ilk POWER makrosu kaç kez MUL çalıştırır?
Çözüm. 65 536 kez. MUL döngünün başında olduğu için önce bir kez çalışır. LOOP CX'i 0'dan FFFFh'ye indirir ve atlar. Döngü CX yeniden 0 olana kadar sürer: CX = 0 → FFFFh → … → 1 → 0. base = 2 ise result = 0000h, base = 1 ise result = 0001h çıkar. JCXZ ile sayaç önceden denetlenirse hiç MUL çalışmaz ve result = 1 olur.
Alıştırma 6. Bir makronun AX dışında hiçbir yazmacı bozmaması için hangi komutlar eklenmelidir?
Çözüm. Gövdenin başına, makronun değiştirdiği her yazmaç (AX hariç) için bir PUSH; sonuna aynı yazmaçlar için ters sırada POP eklenir. Düzeltilmiş POWER'da bu PUSH BX, PUSH CX, PUSH DX ve POP DX, POP CX, POP BX'tir. MUL gibi örtük olarak yazmaç değiştiren komutlar (MUL DX'i, LOOP CX'i, INT hizmetleri dönüş yazmaçlarını değiştirir) özellikle dikkate alınmalıdır. Bayrakların da korunması gerekiyorsa PUSHF ve POPF kullanılır.
9.1.13Özet
- Makro,
ad MACRO p1, p2 … ENDMile tanımlanan bir kod kalıbıdır. Assembler, makronun kullanıldığı her satırın yerine gövdeyi yazar; buna genişletme denir. İşlemci makroyu hiç görmez. - Makro, kullanıldığı satırdan önce tanımlanmalıdır. Kullanılmayan makro kod üretmez.
- Parametreler değer değil metindir. Değerleri, açılmış satır yürütüldüğünde okunur. Gövdenin değiştirdiği yazmaç parametre olarak verilmemelidir; ifade parametreleri gövdede paranteze alınmalıdır.
- Hangi çağrının geçerli olduğuna açılımda oluşan komut karar verir; hata kullanım satırında bildirilir ama nedeni gövdededir.
- Boyut: makro k · n bayt, prosedür (n + 1) + 3k bayt. Gövde 3 bayttan uzunsa prosedür, başa baş noktasından sonra daha küçüktür.
- Hız: her prosedür çağrısı CALL + RET ile 19 + 8 = 27 tur, iki yığın erişimi ve iki kuyruk boşaltma ekler. Makroda bu maliyet yoktur.
- Makroda RET yazılmaz; RET çağıranın dönüş adresini ya da yığında ne varsa onu alır.
- Etiketli makrolar LOCAL ile yazılır; assembler her açılımda ??0000, ??0001 … gibi benzersiz adlar üretir.
- Makronun değiştirdiği yazmaçlar PUSH ile saklanıp ters sırada POP ile geri yüklenir (PUTC, düzeltilmiş POWER).
- LOOP CX = 0 ile 65 536 kez döner; sayacı değişken olan döngülerin önüne JCXZ konur. MUL, DX'i de değiştirir.
- INCLUDE kütüphane metnini kaynağa ekler; emu8086.inc kısa işleri makro, uzun işleri prosedür olarak sunar.
- &, <>, !, % ve ;; makro işleçleri metni birleştirir, gruplar ve hesaplar.
- REPT, IRP ve IRPC adsız, yerinde açılan tekrar bloklarıdır;
=sembolüyle derleme anında tablo üretirler. - IF, IFE, IFB, IFNB, IFIDN, IFDIF koşulu derleme anında değerlendirir; seçilmeyen dal makine koduna girmez.
9.1.14Kavram listesi
- makro macro
- makro genişletme macro expansion
- LOCAL direktifi LOCAL directive
- koşullu derleme conditional assembly
- tekrar bloğu (REPT / IRP / IRPC) repeat block
- direktif directive
- assembler assembler
- prosedür procedure
- prosedür çağrısı procedure call
- dönüş adresi return address
- yazmaçların korunması register preservation
- etiket label
- kütüphane library
- saat turu clock cycle
- önden getirme kuyruğu prefetch queue
- yol döngüsü bus cycle
- liste çıktısı listing file
- öykünücü emulator
9.1.15Kendini Dene
TOPLA makrosu (
MOV AX, x·ADD AX, y·MOV z, AX) AX = 5, BX = 7 ikenTOPLA BX, AX, CXile çağrılıyor. CX ne olur?Cevap
Açılım
MOV AX, BX·ADD AX, AX·MOV CX, AX'tir. Birinci satır AX'i 7 yapar; y = AX metni artık 7'yi gösterir. ADD 7 + 7 = 14 verir: CX = 000Eh, beklenen 12 değil.ALAN MACRO en, boy/MOV AX, en*boy/ENDMmakrosununALAN 10-2, 3çağrısı AX'e ne yükler? Parantezli sürüm ne yükler?Cevap
Açılım
MOV AX, 10-2*3'tür; çarpma önce yapılır: 10 − 6 = 4. Parantezli(10-2)*(3)= 24 (0018h) verir. İki durumda da çarpma derleme anında yapılır ve makine kodunda yalnız bir MOV vardır.Gövdesi 4 bayt olan bir iş kaç kullanımdan itibaren prosedürle daha az yer tutar?
Cevap
Makro 4k, prosedür 5 + 3k bayt. 4k > 5 + 3k için k > 5. Beş kullanımda ikisi de 20 bayttır; altı kullanımdan itibaren prosedür küçüktür (24'e karşı 23 bayt).
Etiket içeren ama LOCAL kullanmayan bir makro programda yalnız bir kez kullanılırsa derleme hatası olur mu?
Cevap
Hayır. Tek açılımda etiket bir kez tanımlanır ve program derlenir. Hata, makro ikinci kez kullanıldığı anda Duplicate declaration olarak çıkar. Bu yüzden makro bir kez kullanılacak olsa da LOCAL yazılır.
MyMacro2 (AX = 2 ise label1'e, AX = 3 ise label2'ye atlar; label1: INC AX, label2: ADD AX, 2) AX = 0 ile çalıştırılırsa AX ne olur?
Cevap
İki JE de atlamaz ve akış label1'e düşer. INC AX 1'i, ADD AX, 2 ise 3'ü verir.
KAYDIR makrosu (n doluysa
MOV CL, n·SHL r, CL) CX = 5 ikenKAYDIR CX, 2ile çağrılıyor. Programcı 5 × 4 = 20 bekliyor. CX ne olur?Cevap
MOV CL, 2, CX'in düşük baytını ezer: CX = 0002h.SHL CX, CL2'yi 2 bit kaydırır: CX = 0008h. Gövdenin bozduğu yazmaç (CL, dolayısıyla CX) parametre olarak verilmiştir; IFIDN ile bu çağrı yakalanabilir.n = 1·tablo LABEL BYTE·REPT 4·DB n·n = n * 2·ENDMbloğu belleğe hangi baytları yazar? Bu blok çalışma anında kaç komut yürütür?Cevap
01h, 02h, 04h, 08h. Blok yalnız veri üretir; n derleme anında yaşayan bir semboldür. Çalışma anında hiç komut yürütülmez.
5 MHz'lik bir 8086'da bir prosedür 1000 kez çağrılıyor. CALL ve RET'in getirdiği ek süre ne kadardır?
Cevap
1000 × (19 + 8) = 27 000 tur. Bir tur 200 ns olduğundan 27 000 × 200 ns = 5,4 ms. Makro kullanılsaydı bu süre olmazdı ama kod 1000 kopya kadar büyürdü.
emu8086.inc'deki PRINT makrosu
PRINT 'Merhaba'için kaç bayt üretir? Dizgi neden kodun içinde olduğu hâlde işlemci onu komut olarak yürütmez?Cevap
'OK' için 27 bayt; her ek karakter bir bayt ekler. 7 karakterlik metin için 25 + 7 = 32 bayt. Makro dizginin önüne bir JMP koyar ve işlemci dizginin baytlarının üzerinden atlar; dizgiye yalnız
MOV AL, CS:[SI]ile veri olarak erişilir.
9.1.16İleri okuma
- Microsoft, MASM Programmer's Guide: makro tanımı, LOCAL, makro işleçleri, tekrar blokları ve koşullu derleme direktiflerinin başvuru anlatımı.
- K. R. Irvine, Assembly Language for x86 Processors, Bölüm 10: makroları, koşullu derlemeyi ve tekrar bloklarını çok sayıda örnekle işler.
- emu8086 yardım belgeleri, 8086 Assembler Tutorial, Part 10 (Macros): emu8086'daki makro yazımı ve emu8086.inc kütüphanesi.
- B. B. Brey, The Intel Microprocessors: makro ve prosedür seçimini 8086 komut süreleri ve donanım yapısıyla birlikte ele alır.
9.1.17Kaynaklar
- emu8086 yardım belgeleri: 8086 Assembler Tutorial, Part 10 (Macros) ve
emu8086.inc. - Microsoft, MASM Programmer's Guide — MACRO, ENDM, LOCAL direktifleri.
- K. R. Irvine, Assembly Language for x86 Processors, 7. baskı, Pearson, 2014 — Bölüm 10 (Makrolar).
- B. B. Brey, The Intel Microprocessors, 8. baskı, Pearson, 2009.
- Intel, 8086 16-Bit HMOS Microprocessor veri sayfası (komut süreleri).
İlgili sayfalar: Bölüm 9.1 sunumu · Çalışma soruları · Terim sözlüğü · © Sercan Külcü. Tüm hakları saklıdır.