Temel C Programlama -34- İşaretçi Aritmetiği

Buraya kadar işaretçi değişkenlerine sadece bir adres değeri atadık ve o değer öylece kaldı. Fakat bunlar bir değişken olduğuna göre içindeki değerler ile aritmetik işlemler de yapılabilmeli değil mi? Bu konuda işaretçiler karşımıza değişkenler kadar esnek halde çıkmamaktadır. Bunun sebebi ise oldukça basittir. Bir adres verisini çarpmak, bölmek veya mod almanın ne gibi bir anlamı olabilir? Değişken aritmetiği zaten diziler üzerinde çalışırken işimize yaramaktadır. Eğer bir dizi üzerinde çalışıyorsak dizinin ilk elemanını işaret eden işaretçinin değerini bir artırırsak artık o dizinin ikinci elemanını işaret edececektir. Ama sıradan değişkenlerde bu bizi rastgele bir değere götürecektir. O yüzden işaretçi aritmetiğini kullanacağımız yerler sınırlı olduğu gibi sınırlı yerlerde de dikkatli kullanmamız gerekecektir.

Özetlemek gerekirse biz işaretçilerle şu işlemleri yapabiliriz:

  • Artırma veya Azaltma

  • Toplama veya Çıkarma

  • Karşılaştırma

  • Atama

Dizi ve Fonksiyonlar

Örnekleri vermeden önce size diziler ve fonksiyonlara ait bir özelliği hatırlatmak istiyorum. Bildiğiniz üzere dizi ve fonksiyonları tanımlarken aynı bir değişken tanımlar gibi bir ad veriyor ve dizilere elemanları ve fonksiyonlara da kodları yerleştiriyorduk. Bu verdiğimiz ad bize ne değerini geri döndürebilir? Bu verdiğimiz adlar aslında birer işaretçi olup dizide dizinin ilk elemanının adresini fonksiyonda ise fonksiyon blokunun başladığı hafıza adresini bize verecektir. Yani fonksiyon çağırıldığında mikroişlemciye hafızada yer alan şu adrese git diye bir komut verilmektedir. Dizide ise dizinin ilk elemanının adresi verilmekte ve hafızada sırayla okunmaktadır.

Şimdi bir fonksiyonun adresini ekrana nasıl yazdırdığımızı görelim.

#include <stdio.h>
#include <stdlib.h>
void kare_al( int *kareptr);
int main(int argc, char *argv[]) {
    printf("kare_al fonksiyonunun adresi: %p",kare_al);

    return 0;
}

void kare_al( int *kareptr)
{

}

Gördüğünüz gibi yine printf ile ve %p formatında fonksiyonun adresini ekrana yazdırıyoruz. Burada kare_al diyerek kare_al fonksiyonunun adresini yazdırmış olduk. Bu biraz tuhaf gibi görünse de C dili buna imkan vermekte fonksiyonların adresini ekrana yazdırabilmektedir. Ekran görüntüsü şu şekilde olabilir.

Fonksiyonun adresini öğrenmenin bize ne katacağı fonksiyon işaretçisinin konusu olduğundan ben asıl anlatmak istediğim konu olan dizilere geçmek istiyorum. Çünkü bundan sonra yapacağımız işlemlerde diziler bol bol yer almak zorundadır. Diziler daha önce bahsettiğim gibi aslında birer işaretçidir ve adları bize adres değerini geri döndürmektedir. Dizinin ilk elemanının adres değerini öğrendikten sonra o adres değeri üzerinde aritmetik işlem yapıp dizinin diğer elemanlarına alt seviyeden erişme imkanımız olacaktır. Bu sayede büyük boyutlu dizilerde daha hızlı çalışma imkanımız olur. Şimdi bir dizi tanımlayalım ve onun adresini öğrenelim.

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
    int dizi [5] = {10, 11, 12, 13, 14};
    printf("dizi dizisinin adresi: %p",dizi);
    printf("\ndizi dizisinin ilk elemani: %i", *dizi);

    return 0;
}

Burada dizi adında 5 elemanlık bir dizi tanımladık ve dizi elemanlarına ilk değerlerini verdik. İlk değerlerini vermemiz sonrasında üzerinde işlem yaptığımızda bize yardımcı olacak. Sonrasında yukarıda fonksiyonun adresini öğrendiğimiz gibi %p biçimlendiricisiyle dizi dizi değişkenini yazdırdık. Dikkat ediniz, burada addressof (&) operatörü kullanılmayıp aynı işaretçilerde olduğu gibi düz bir şekilde yazıldı. Normal durumda değişkenler değeri dizi ve fonksiyonlar ise adresi vermektedir. Yalnız biz *dizi şeklinde yani erişim operatörüyle kullanırsak bu sefer de ilk elemanın değerini bize verecektir. O halde biz erişim operatörüyle de bütün dizi elemanlarına erişip bunları değiştirme imkanına sahip oluruz. Programın çıktısı şu şekilde olabilir.

İşaretçilerle Aritmetik İşlemler ve sizeof() Operatörü

Dizi ve fonksiyonların özel konumunu öğrendiğinize göre artık diziler üzerinde işaretçi aritmetiğini kullanarak nasıl işlem yapabileceğimizi göreceğiz. Burada kullanacağımız operatörler karşılaştırma operatörleri ile beraber +, -, ++, — ve = operatörleridir. Diziler üzerinde işlem yapmadan önce de sizeof() operatörünün ne işe yaradığını görmenizi istiyorum. Çünkü işaretçilerle dizi üzerinde çalışırken dizinin boyutunu öğrenmeniz gereklidir. En basit olarak bir dizi içerisindeki bütün küçük harfleri büyük harfe çeviren bir for döngüsü oluşturduğumuzu varsayalım. Bunun için döngünün kaç kere tekrarlayacağını makine ancak dizinin boyutunu bilmekle öğrenebilir. Burada sabit boyutlu bir dizi için çok zor bir durum olmasa da dinamik dizilerde zorunlu olmaktadır. Elbette daha dinamik dizileri ve hafızada yer ayırmayı anlatacak değiliz. Fakat sizeof() operatörğünü kullanmaya alışmanız sizin için daha pratik olacaktır. Şimdi sizeof() operatörüyle belli başlı değişkenlerin boyutunu ve yukarıda verdiğimiz dizinin boyutunu görelim.C

#include <stdio.h>
#include <stdlib.h>
void kare_al( int *kareptr);
int main(int argc, char *argv[]) {
	int dizi [5] = {10, 11, 12, 13, 14};
	printf("bir tamsayinin (int) boyutu %i \n", sizeof(int));
	printf("bir double degiskenin boyutu %i \n", sizeof(double));
        printf("dizi dizisinin boyutu: %i", sizeof(dizi) / sizeof(dizi[0]) );
	return 0;
}

Burada biz sizeof(int); yazdığımızda sizeof fonksiyonu tam sayı yani integer tipinde verinin büyüklüğünü bize geri döndürecektir. Bir integer de 32 bitlik bir derleyicide 4 baytlık bir yer kaplamaktadır. Aynı şekilde double ise 8 baytlık bir yer kaplar. Biz buraya sizeof(int) şeklinde yazabiliriz. Biraz kural dışı gibi görünse de aslında tamamen doğrudur. Bir dizinin boyutunu yani kaç elemandan oluştuğunu öğrenmenin yolu ise o dizinin toplam boyutunu yani toplamda kaç bayt olduğunu hesaplayıp sonrasında ise bunu bir elemana bölmektir. Bu sayede dizide kaç eleman olduğunu bulabiliriz. Aynı şekilde dizinin bir elemanının boyutunu ise sizeof(dizi[0]) ile bulabiliriz. Çünkü dizinin ilk elemanı ile son elemanı her zaman aynı tiptir. Programın ekran çıktısı şu şekilde olacaktır.

Şimdi ise işaretçilerle nasıl aritmetik işlem yaptığımıza bakalım. Biz yukarıda dizinin ilk elemanının değerine erişmiştik. Acaba sadece aritmetik işlemlerle farklı elemanlara da erişebilir miyiz bir buna bakalım.

#include <stdio.h>
#include <stdlib.h>
void kare_al( int *kareptr);
int main(int argc, char *argv[]) {
	int dizi [5] = {10, 11, 12, 13, 14};
    int *diziptr = dizi;
    printf("Dizinin ilk elemani: %i", *diziptr);
    printf("\nIsaretcinin adres: %p", diziptr);
    diziptr++;
    printf("\nSonraki Eleman: %i", *diziptr);
    printf("\nIsaretcinin Adresi: %p", diziptr);
    diziptr+=2;
    printf("\nIki adim sonraki eleman: %i", *diziptr);
	return 0;
}

Burada int diziptr = dizi; şeklinde bir değer atama yolunu tercih ettik. İşaretçilere böylece ilk değeri kolayca atayabilirsiniz. Sonrasında ise her zaman olduğu gibi diziptr ile işaretçinin işaret ettiği değeri ekrana yazdzırdık. Sonrasında diziptr++; işlemini yaptık. Bu işlem doğrudan diziptr işaretçisinin değerini etkilemektedir. Yani içinde bulundurduğu adres değeri 0x50 ise 0x51 olmaktadır veya işaretçinin boyutu ne kadarsa adres değeri ona göre artmaktadır. Burada 32 bitlik bir derleyicide int tipindeki bir işaretçinin boyutu 4 baytlık olduğu için her artırımda dörder dörder artmaktadır. Kısacası biz işaretçiyi bir artırdığımız zaman dizinin bir sonraki elemanını işaretler hale gelecektir. Eğer işaretçiyi iki artırırsak bu sefer de adres değeri bu durumda 8 artacak ve iki adım atlayıp o sıradaki elemanı gösterecektir. Biz hangi elemanda olduğumuzu ise programda sıfırıncı eleman ile sizeof() operatörü ile bulduğumuz dizi boyutu sayesinde buluruz. Programın ekran çıktısı şu şekilde olacaktır.

Şimdi bir sorun düşünelim ve buna çözüm getirmeye çalışalım. Bizim bir işaretçimiz var ve bu bir dizideki bir elemanın adres değerini barındırmakta. Fakat biz bu elemanın dizinin kaçıncı elemanı olduğunu bilmiyoruz. Bunu öğrenmek için bir program yazalım.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    int dizi [5] = {10, 11, 12, 13, 14};
    int *diziptr;
    int rastgele_sayi = (rand()) % 5; 
    diziptr = &dizi[rastgele_sayi];
    // diziptr artık benim de bilmediğim bir üyeyi gösteriyor!
    
    int sayi = (int)(diziptr - dizi);
    printf("%i", sayi);
    return 0;
}

Bu programda rand() fonksiyonu ile ürettiğimiz sayının 5’e bölümünden kalanını elde ettik. Bu bize 0 ile 4 arasında bir sayı verecek ve işaretçimize bu ürettiğimiz rastgele sayıyı atayacağız. Bu sayının rastgele olmasını programın doğruluğunu göstermek için tercih ettik. Bu bir kontrolümüz dışında değere sahip olabilen bir değişken de olabilir. Kısacası bizim diziptr işaretçimiz dizi[0], dizi[1], dizi[2], dizi[3] ve dizi[4] elemanlarından herhangi birinin adresini alabilir.

Sonrasında ise yaptığımız elimizde bulunan dizi elemanının adresini dizinin adresinden çıkarmak oldu. Çünkü dizi elemanlarının adresi dizinin adresine eşit veya ondan fazladır. Başta söylediğimiz gibi dizinin ilk adresi dizinin 0 numaralı elemanını işaret eder. O halde kalan bize dizinin kaçıncı elemanı olduğunu gösterecektir. Kalan sayıyı (int) ile integer formatına çevirip ekrana yazdırdığımızda program doğru bir şekilde bize rastgele_sayi ile seçtiğimiz bilinmeyen dizi konumunu bize gösterecektir. Bunu yapabilmeyi işaretçileri birbirinden çıkarabilmeye borçluyuz.

Buraya kadar anlattıklarımızın şimdilik yeterli olacağını düşünüyorum. İleride uygulamalarla beraber karşımıza işaretçi aritmetiği ve karşılaştırması çıkacaktır.

Last updated