Daha önce üst seviye
programlama dilleri ile uğraştıysanız değişkenler hakkında bilginiz muhakkak
vardır. x86 assembly dilinde de programcılar programlarında kullanacakları bazı
verileri, değişken olarak tanımlarlar. Aslında bunlar byte yada bytelardan
oluşan dizilerdir. Hafızadaki bu veriler, programınızdaki kullanım şekline göre
değişik haller alabilir.
DEĞİŞKENLERİ
DEKLARE ETMEK
Örneğin 2 sayıyı
toplayan program yazmayı düşünelim. Bunu 2 değişik şekilde yapabiliriz.
Birincisi acil adresleme modu ile;
MOV
AL,5
ADD AL,4
Bu örnekte hiçbir
değişken tanımlanmamıştır ve bu program parçası sadece 5 ile 4 ü toplar ve
sonucu işlemcinin içindeki AL kaydedicisinde saklar. Tabi bu programın ne kadar
kullanışlı olduğunu artık siz tahmin edin. Oysa bu sayıları birer değişken
olarak tanımlasaydık daha esnek bir program hazırlamış olurduk.
.DATA
sayi1
DB 5
sayi2 DB 4
toplam DB ?
.
.
.
.CODE
.
.
MOV AL,sayi1
ADD AL,sayi2
MOV toplam,AL
Yukarıdaki program
parçasında 3 adet bir byte’lık değişken tanımlanmıştır. Bunlar sayi1, sayi2 ve
toplam değişkenleridir. Aslında bu yazdığımız satırlar assembler’a bir demeç
şeklindedir. Assembler programımızı derlerken, hafızada bizim kullanmamız için
3 byte’lık alan ayıracak ve bu alanlardan birine 5 diğerine 4 değerlerini
yerleştirecektir. Toplam değişkeninin değeri program çalıştığında önemli
olacağından dolayı, onun değeri henüz atanmamış fakat şundan da emin olun ki o
adreste muhakkak bir değer mevcuttur.
Gördüğünüz gibi
değişkenlerini isimleri, türleri ve değerleri mevcut. Değişken ismi (sayi1 gibi)
aslında bir offset adresidir ve kullanımı seçimliktir. Şayet değişken
tanımlarken isim kullanmazsanız assembler yine hafızada değişkenin türüne göre
yer ayırır. Fakat böyle bir durumda değişkeni programınızın içinde kullanmak
için adresini (ds:0000 gibi) bilmeniz gerekir. Çok sayıda değişken tanımlarken
bu adresleri hesaplamak güç olacağından dolayı değişkenlere isim vermemek pek
tavsiye edilmez. Değişkenin türü ise boyutu ile alaklıdır, örneğin DB ile
değişkenimizin 1 byte’lık olacağını belirtiyoruz. Son olarak değişkenin değeri
tabi ki bir rakam yada sayı olmak durumunda. Şayet başlangıçta değer önemli
değilse ? operatörünü kullanabilirsiniz.
Kaynak kodda
değişkenleri bu şekilde yazarak assemblera hafızada yer tahsis etmesini
söylemiş, bir başka deyişle değişken deklare etmiş oluyoruz.
İşaret Karmaşası
1 byte’lık bir
hafıza alanına ne sığar? Toplam 256 adet farklı değer 1 byte ile tanımlanabilir.
Bunlar onluk düzende 0-255 arası sayılar veya -128’den 127 ye kadar olan
işaretli sayılar olabilir. Kafanız karışmasın bu değerleri aslında binary olarak
düşünmek lazım, çünkü 0...255 ve -128...127 arası sayılar toplam 512 adet
ediyor. Aslında, bunlar sadece 1’lerin ve 0’ların kombinasyonlarıdır. Yani 1
byte 0000 00002 ile 1111 11112 arasında değişen
kombinasyonlardan oluşur.
Günlük hayatımızda
işaretli bir sayıyı tanımlamak ne kadarda kolay değil mi? Başına - işaretini
getirdiğimiz zaman işlem tamamdır. Fakat bilgisayarlarda 1 ve 0 dan başka
kullanacağınız bir değer yok. İşaretli veya işaretsiz sayılar, harfler,
metinler, resimler, mp3’ler ve Tomb Raider :) tamamen 1 ve 0’lardan oluşur. İşte
bu 1 ve 0’ları program içinde yorumlamak önemlidir, ancak o zaman anlam
kazanırlar.
Aslında işlemci
(CPU) sadece matematiksel, mantıksal ve veri taşıma işlemleri yapar. Bu bağlamda
bizim için alt seviyede önemli olan 1 ve 0’lardır. O zaman işaretli ve işaretsiz
sayılar neye göre belirlenecek? Boyutlar ve işlemci durum kaydedicisi (flag
register) burada imdadımıza yetişiyor. Örneğin 1111 1111 değeri onluk düzende
255’mi yoksa -1’mi.
255’tir, çünkü 1111
11112 = 20+21+22+23+24+25+26+27
= 255
-1’dir, çünkü 1 ile
toplandığında 1 byte’lık alanda 0000 00002 değeri elde edilir ve elde
biti oluşur.
Şekil 1 - 1 byte’lık
bir değer 1 byte’lık
başka bir değerle toplanınca sonuç 1 byte’lık olmak zorundadır.
1 byte’lık bir değer 1 byte’lık
başka bir değerle toplanınca sonuç bir byte’lık olmak zorundadır, çünkü ayrılan fiziksel alan bu
kadardır. Bu işlemde artan 1 bitlik değer ilkokul matematiğinden bildiğimiz
elde’dir ve bu elde işlemci durum kaydedicisinin C (carry) bitinde saklanır.
Daha önce işlemci durum kaydedicisinin bitsel olarak önemli olduğundan
bahsetmiştim.
Bu kargaşa tamamen boyutların sınırlı olmasından
kaynaklanıyor. Ayrıca bilgisayarın bu hesap mantığı ile pozitif bir sayı bir
başka pozitif sayı ile toplandığında negatif bir değer de elde edilebilir. Bunu
tam tersi de olabilir. Biz bunları ilerleyen makalelerimizde, işlemci durum
kaydedicisini anlatırken işaret ve boyut taşması olarak inceleyeceğiz. Şimdilik
siz matematiksel hesaplamalar yaparken alanlarını kısıtlı olduğunu göz ardı
etmeyin. Yani 2 sayıyı toplayan program yerine, sonuç olarak maksimum 255
değerini gösteren program demek şu anda daha doğru olur :)
BASİT
DEĞİŞKENLER
Byte Değişkenleri
Bir byte boyutunda
değerleri deklare etmek istiyorsak genellikle DB direktifini kullanıyoruz. DB
İngilizce’de Define Byte yani byte tanımla anlamına gelir. Bu direktif ile
hafızada 1 byte’lık bir alan tahsis eder ve bu alana adresi ile erişebilirsiniz.
MASM ile DB direktifi yerine byte yada sbyte direktiflerini de kullanabilirsiniz, fakat
sonuçta assembler’ın yapacağı iş aynıdır.
i db 0
j byte 255 ;MASM için alternatif direktif
k sbyte -1 ;MASM için alternatif direktif
Yukarıdaki üç
tanımlama farklı gibi gözükse de assembly dili yazımı değişkenleri tanımlarken
boyut dışında bir sınır koymaz. Örneğin k değişkenine sonradan "mov k, 125"
komutu ile pozitif bir değer atayabiliriz. Yapılamayacak olan ise "mov i, 300"
veya "mov j, -150" gibi 1 byte’a sığmayacak değerleri bu değişkenlere atamaktır.
DB direktifi ile;
Ayrılan Hafıza Alanı
: 1 Byte
Tanımlanabilecek Değer Aralığı: -128...255
Word Değişkenler
DW 8086’dan 80286 işlemcisine kadar offset
adresleri, işaretli ve işaretsiz tamsayılar tanımlamak için kullanılan en geniş
veri tipiydi.
-32768...65535
arasındaki tam sayları DW direktifi ile deklare edebiliriz. MASM için word ve
sword direktifleri de aynı işi görür. Word değişkenler hafızada 16 bit yani 2
byte’lık alan kaplarlar.
sayi1 |
dw |
? |
sayi2 |
dw |
? |
isayi1 |
dw |
? |
sayi_0 |
dw |
0 |
sayi_eksi |
dw |
-1 |
sayi_enbuyuk |
dw |
65535 |
pointer
|
dw
|
sayi1 |
Özetle DW direktifi
ile;
Ayrılan Hafıza Alanı
: 2 Byte
Tanımlanabilecek Değer Aralığı: -32768...65535
Double Word Değişkenler
4 byte’lık
değişkenler tanımlamak için kullanılır. DD direktifi ile bu türdeki değişkeleri
tanımlayabilirsiniz. MASM ile dword, sdword direktiflerini de kullanabilirsiniz.
sayi1 |
dd |
? |
sayi2 |
dd |
? |
isayi1 |
dd |
? |
sayi_0 |
dd |
0 |
sayi_eksi |
dd |
-1 |
sayi_enbuyuk |
dd |
4000000000 |
pointer
|
dd
|
sayi2 |
DD direktifi ile;
Ayrılan Hafıza
Alanı : 4 Byte
Tanımlanabilecek Değer Aralığı: -2,147,483,648...4,294,967,295
6, 8 ve 10 Byte’lık Değişkenler
DF direktifi ile 6,
DQ direktifi ile 8 ve DT direktifi ile 10 byte’lık değişkenler tanımlanır. Fakat
bu direktifler aslında kayar noktalı BCD (ikilik düzende kodlanmış onluk
sayılar) sayılar için kullanılır. Bu 3 direktifin asıl kullanım amaçları aşağıda
açıklanıyor.
DF: 80386 ve üstü
işlemcilerde, 32 bitlik korumalı modda, 48 bitlik pointer (indeksleme amacıyla,
offset adreslerini göstermek için kullanılan bir işaretçi) olarak kullanılır. Bu
direktif ile 6 byte’lık değişkenlerin tanımlanması mümkün olsa da 80386 ve
sonrası işlemcilerde pointer olarak kullanılır.
DQ: 64 bitlik kayar noktalı sayları 64 bitlik
tamsayıları deklare etmek için kullanılır. Daha çok kayar noktalı sayıları
deklare etmek için kullanılır, çünkü x86 ailesinin henüz 64 bitlik genel amaçlı
bir kaydedicisi yoktur. Yani bu direktif ile 64 bitlik bir tam sayı deklare
ettiğinizde, bu sayı ile doğrudan işlem yapamazsınız çünkü en büyük kaydedici 32
bitliktir.
DT: Daha hassas (80
bitlik) kayar noktalı sayılar ve 10 byte’lık BCD değerleri deklare etmek için
kullanılır.
Yukarıdaki
direktiflerle ilgili ayrıntılı bilgileri kayar noktalı sayılarla işlemler
başlığı altında ilerleyen makalelerimizde vereceğim.
Pointer Veri Tipleri
Pointer işaretçi
anlamına gelip adresleme modlarında gördüğümüz indeksli adresleme ile
alakalıdır. x86 uyumlu işlemciler iki tür pointer’ı desteklerler. Bunlar near
(yakın) ve far (uzak) pointer’lardır. Buradaki yakın ve uzak kavramı referans
alınan adresin segmenti (64Kb.) aşmaması yada aşması durumunu ifade eder.
Near pointer’ler 16
bitlik’tir ve bir segmentin offsetlerine erişmek için kullanılırlar. p bir
değişken olsun, p=1000h için aşağıdaki kodları inceleyelim.
mov
bx, p ;BX’e p
pointerını yükle.
mov ax, [bx] ;p’nin
gösterdiği konumdaki veriyi AX’e getir.
Örnekte AX
kaydedicisinin değeri pointer’ın işaret ettiği adresten alınacak 2 byte’lık
değer ile değişecektir. Bir başka deyişle DS:1000h ve DS:1001h adresindeki
değerler AX’e yüklenecek. Bu işlem aynı segment içinde olduğundan dolayı,
pointer near yani yakın bölgedeki bir adresi göstermiş oldu. Aslında mov ax,ds:1000h
komutu da aynı işi yapardı fakat pointer değişkeni kullanılmadığından sonradan
pointer değerini değiştirip aynı komut satırı ile hafızanın başka bir bölgesine
erişmek mümkün olmazdı.
Far (uzak)
pointer’lar ile hafızanın istediğiniz bölgesine erişip işlem yapabilirsiniz.
Bunun için pointer deklare edilirken DD direktifinin kullanılması gerekir, yani
pointer 32 bitlik olmalıdır. BX, BP, SI ve DI kaydedicilerini erişeceğiniz uzak
offsetler için herhangi bir segment kaydedicisini de (ki bu genellikle ES olur)
erişmek istediğiniz segment için kullanabilirsiniz. İsterseniz bunu bir örnek
ile açıklamaya çalışalım.
5555:3333 adresine
erişip, buraya FFh değerini kopyalamaya çalışacağız. Bu işlemi yapan program
aşağıdaki gibidir. Kaynak kodları buradan
download edebilirsiniz.
Şekil
2 - Uzak adreslere erişmek için 32 bitlik pointer’lar kullanılır.
Bu örnekte pointer
32 bitlik’tir ve erişeceğimiz segment:offset adresini gösterir. LES komutu ES
kaydedicisine pointer değişkeninin yüksek değerlikli 2 byte’ını (5555h)
yüklerken, operandında gösterilen kaydediciye de (bu örnekte BX) düşük
değerlikli 2 byte’ını (3333h) yükler. MOV ES:[BX], AL komutu ile de 5555:3333
adresine AL deki FFh değeri kopyalanır. Aşağıdaki şekiller ile bu programın
pointer kullanarak nasıl uzak adreslere eriştiğini görebilirsiniz.
Şekil
3 - LES BX, P komutu işlenmeden önce ES ve BX’in durumu
Şekil
4 - Pointer değişkeni ilgili kaydedicilere yüklendikten sonra
Şekil
5 - 5555:3333 adresine FF değeri yüklendikten sonra
ÇOKLU DEĞİŞKENLER
Bu tür değişkenler
birden fazla parçadan oluşur. Tek boyutlu ve çok boyutlu diziler (arrays) ve
yapılar (structures) bu tür değişkenlerdir. Bu makalede sadece tek boyutlu
dizileri anlatmaya çalışacağım, çok boyutlu diziler ve yapıları başka bir
makalede güzel örneklerle, amacına uygun bir şekilde anlatacağım.
Diziler
Dizilerlerde diğer
değişkenler gibi hafızaya yerleştirilmiş verilerden oluşur. Ard-arda gelen byte,
word veya double word değişkenler dizileri oluşturabilir. Daha önceki
makalelerimizde "Merhaba Assembly" yazısını ekrana yazdırmıştık, işte bu
karakterlerde hafızada dizi olarak saklanır. Şu anda yazdığım karakterlerde
hafızada bir dizi veri olarak saklanacak.
Dizi kavramının
yanında getirdiği bazı terimler vardır, bunlar indeks, taban adresi, dizi
elemanı ve eleman tipi’dir. Burada dikkat edilmesi gereken en önemli nokta
diziyi oluşturacak elemanların aynı türden olma koşuludur. Diğer terimleride
isterseniz aşağıdaki şekille açıklamaya çalışalım.
Şekil
6 - Diziler ile ilgili terimler.
Dizinin tüm
elemanlarının aynı türden olması gerektiğini söylemiştik, bununla beraber
dizinin ilk elemanının adresi taban adresidir. Dizinin elemanlarına erişmek için
bu adres referans olarak alınabilir. Son olarak dizinin ilk elemanından itibaren
adresler artarak devam eder ve bu adreslerdeki her elemana indeks değerleri ile
erişilir. Dizinin herhangi bir elemanına erişmek için kullanılacak klasik
formül;
Eleman_Adresi =
Taban_Adresi + ((Indeks - İlk_Index) x Eleman_Boyutu)
Bu formülde
ilk_indeks 0 ise göz ardı edilebilir.
Dizilerin Deklare Edilmesi
Başlangıçta elemanların değerleri önemli değilse;
Dizi
DB 100 DUP (?) ;byte
türünden 100 elemanlı bir dizi
Dizi DW 100 DUP (?)
;word türünden olduğundan dolayı hafızada 200 byte’lık alan kaplar.
Buradak DUP
direktifi ingilizcedeki duplicate (diğerinin aynısı) kelimesinin karşılığıdır.
Elemanlar aynı olacaksa;
Dizi
DB 100 DUP (12h) ;
Hafızaya 100 adet 12h yerleştirir.
Elemanlar farklı ise;
Dizi
DB 32, 45, 67, 3F, 0, 7, 11 ;7 tane byte
türünden elemandan oluşan bir dizi.
Yazi DB ’Merhaba Assembly’
;16 elemanlı bir karakter dizisi, hafızada ascii kod karşılıkları saklanır.
Unutulmaması gerekir
ki taban adresi dizinin başında yazdığımız etikettir, örneğin yukarıda ’Merhaba
Assembly’ karakterlerinden oluşan byte türündeki dizinin taban adresi
Yazi’dır.
Tek Boyutlu Dizilerin Elamanlarına Erişmek
WordDizi
DW 1, 45, 1234h, 567Ah, 99, 105Eh
Yukarıdaki dizinin
2. elemanına erişmek demek o elemanın adresini bulmak ile aynı anlama gelir.
Bunun için formülümüz vardı;
Eleman_Adresi =
Taban_Adresi + ((Indeks - İlk_Index) x Eleman_Boyutu)
Bu formülü word
tipindeki dizimize uygulayacak olursak;
Eleman_Adresi =
WordDizi + ((2 - 0) x 2)
yada;
Eleman_Adresi =
WordDizi + 2 olarak bulunur.
Tabi ki bu işlemi
bir program ile yapmak lazım. Aynı işi yapan program parçası aşağıda
verilmiştir.
mov
bx, 2
; indeks değeri BX’e yükleniyor,
add bx, bx
; 2*bx işlemi yapılıyor,
mov ax, WordDizi [bx] ; AX’e dizinin 2.
elemanı (1234h) yükleniyor.
Dizi elemanlarına
erişmek için çok değişik teknikler mevcut, fakat bunları ilerleyen
makalelerimizde açıklayacağım. Bazen assembly programcıları dizilerini deklare
ederken, dizinin ilk elemanı olarak indeks değerini koyarlar. Tabi ki bu durumun
doğruluğu görecelidir. Örneğin byte türünden bir dizi için bu teknikle en fazla
255 elemandan oluşan bir dizi deklare edebilirsiniz. Daha fazla eleman deklare
etmek için indeks değerinin boyutunu arttırmak gerekir ama bu durumda da dizi
elemanlarının aynı türden olması şartını göz ardı etmemek gerekir.
Diziler usta
programcıların vazgeçemediği veri yapılarıdır. Önemli olan dizinin elemanlarına
erişmek için kullanılacak olan fonksiyonu assembly dilinde yazabilme
kabiliyetidir. Örneğin;
Yildizlar
DB 10 DUP (’*’)
dizisinden
aşağıdaki üçgeni ekrana yazdırmak gibi.
*
**
***
****
Alın size ödev,
bir daha ki makaleye kadar bu programı yazmaya çalışın, böylece hem adresleme
modlarını hem de tek boyutlu dizileri kavramış olursunuz. Sakın zor demeyin elin
oğlu dizilerden matrix türetip, sonra ona felsefi bir boyut ekleyip filim
yapıyor :)
Makale:
X86 Assembly Dilinde Değişken Bildirimi - 1 Assembly ve X86 Programlama Eren Erener
|