1
C Pointers

C Pointers


C İşaretçileri (Pointers)

"Eyy, romalılar! İşaretçileri sizden öğrenecek değiliz! Biz işaretçileri kullanmasını biliriz." demek ile biliyorsunuz ki işaretçiler bilinmiş olmuyor. Yazılım dünyasında genelde C'ciler sıkıntılı tipler olarak karşımıza çıkıyor. Çünkü sıkıntılı insanlar bu dil ile uğraşır. Bir de bu C ile uğraşan kişi Karadenizli ise (ben) sıkıntı daha büyük oluyor. Dhdbdbdbd

Şaka bir yana C dünyasında bir özlü söz vardır. C programcıları ikiye ayrılır; bacaklarından cart diye dhdbcbrbfbf. Teknik anlamda doğru ama mevzumuz bu değil. İkiye ayrılırlar evet ama "İşaretçi kullananlar", "İşaretçi kullanamayanlar". Gördüğünüz gibi kullanmayanlar değil. KullanAmayanlar. Mevzunun bu kadar derin olduğunu görebiliyorsunuz peki neden?

C dünyası dışında bakalım olaylara, Python, Js, Java, C# bunların tipleri dinamik oldun static olsun farketmez hep referans muhabbetini duyarsınız. a=b dediğiniz de referans kopyalaması oluyor muhabbeti var. Çünkü bu diller C ve diğer dillerin karışımı ile yapılmış işaretçiler kullanılarak yapılan diller. İşaretçi adres gösteriyor ya. İşte bu dillerde referans dediğimiz şey adresdeki bloğu. Bu sebeple bu dillerde işaretçi mantığı tam olarak olmadığı gibi gereği de yoktur ve işaretçi aritmatiği de yoktur.

Şimdi gelelim C dünyasına? Sahi ya, işaretçi neydi? İşaretçi emekti. (bkz:Alyazmalım. Dııınn dındıdıdınnn dırıdırıınnn dırıdırıdırıdırırıd)

int x; dediğimizde aslında x yoktur. Bu şey gibi madde diyoruz ama maddenin temelini oluşturan atomların bir katı olmadığı ve Higgs Alanı ile etkileşimde bulunduğundan kütle dediğimiz şeyin ortaya çıkışı gibi birşey. Yani ASM de, {} arasında int x dediğimizde stack alanının olduğu yeri tutan bir değer genelde 4 bayt artar. Aşağıdali kodlara da x yerine bu değer-4 değerine girerek erişiriz. Olay bu aslında x yok. Peki ne var. x in adresi var. Her değişken adreste tutuluyor. Bana adresi verin!

int x dediğimde x in bir ev olduğu düşünün. Bir ev birden fazla yerde olamaz demi? Bir adresi olur. İşte değişkenin adresi de böyle bir adres. &x dediğimiz de x in adresine erişiriz.

Şimdi burada küçük denemeler yapalım.

int x;

&x; x in adresi
*&x; x in adresinin içerisi yani x.
*&x = 10; x in adresinin içerisine 10 koy. Yani x 10.

+ Evet biz bunu bu sebeple kullanıyoruuuzzzzzz demiiii? 
- Hayır.

Peki ben bu x in adresini tutmak istiyorum. Nasıl tutacağım?

2726472 değerini unsigned char tipinde tutamam çünkü maks aldığı bir değer var o da 2 üzeri 8 -1 yani 256 -1=255 (Yılın 256. Günü Programcılar günüdür. 13 eylül. Benim doğum günüm)

Peki bir adres kaç bayt olabilir ve bunu hangi tipte tutmalıyım?

Sisteminde 32 bit işlemci varsa adresler mantıken 32 bittir. Çünkü adres dediğimiz şey ramdeki bir yerdir. Ve bu adrese işlemcinin adres yolu kadarıyla erişebilirim. 32 bit sistemde 32 bit adres yolu vardır. Yani 32/8=4 bayttır. Peki 64 bit sistem olsaydı? Doğru bildiniz mi diyemem 64/8=8 bayt.

+ Peki sistemden sisteme değişen bir şey ise hangi tip oglim?
- size_t tipi. Hani bazı kütüphane dosyaları size_t alıyor. O.

size_t adres;
int x;

adres = &x;

Çok güzel. Şimd adres değişkeninden x e nasıl erişeceğim? 

*((int *)adres) =4;

Sizce böyle bir kullanımı tercih eder miyiz? Haayııırr.

İşte burada işaretçiler devreye giriyor.

int * isaretci; //size_t demedim
int x;

isaretci = &x;
*isaretci = 10;

+Peki neden size_t kullanmadım?
- Gerek yok.

Burada x in adresini tutacak bir işaretçi istiyorum. İşaretçinin de integer bir adres mantığında çalışmasını istiyorum. Ne demek bu?

isaretci bildiğiniz üzere adres tutuyor. Peki int 4 bayt. char 1 bayt. short 2 bayt iken ben bir adrese veri yazdığımda kaç baytının etkilenmesini gerektiğini bildirmem lazım değil mi? İşte bu yüzden int * isaretci;

char * isaretci;
int x;
isaretci = &x;

Bu geçerlidir. Lakin.
*isaretci = 10; dediğimizde. Sadece 1 bayt kullanılır ve ona 10 atılır. int işaretçi olsaydı yukarıdaki gibi. 4 baytını etkileyecek şekilde 10 atılırdı. Bu da 3 bayt 0, 4. Bayt ise 10 olurdu. 10 dan büyük baya büyük değerler için yuvarlama olmadan 4 bayt doldurulurdu. Tabi 4 bayttan büyük olmasın.

İşte buradan sonrası işaretçi aritmatiği. Ama burada bir çentik atıyorum. Dizilere geçiyorum.

int x[5]; dediğimizde stackden 5*sizeof(int) kadar genelde 20 bayt yer ayrılır.

Peki x neyi göstermeli? Düşünmüşler. Başlangıç adresi demişler. Eee işaretçilerde adres tutuyordu? Ama diziler sadece kendi adresini tutar. Değiştirilemez. Halbuki işaretçilere her zaman başka adres atayabiliriz.

int x[4];
int * isaretci;

isaretci = x;
X dediğimiz gibi bir adresti. & Demek mantıksızdır.

*isaretci demek, *dizi demek, dizi[0] demek, isaretci[0] demek artık aynıdır.

Şimdi gelelim isaretçi aritmatiğine.

Bir bardak un, bir bardak şeker ... Bir dk bu o değil. Dndnxbdbc. Komik tm mı?

Ne dedik biz? int * isaretci; dediğimizde isaretci kendisinin integer bir adres tuttuğunu biliyor çünkü int dedik.

*isaretci dediğimizde 4 bayt işleme sokuluyor. * Değil de başka birşey kullansam?

isaretci++; ne olur?

isaretcinin gösterdiği adresten 1 bayt sonrasına mı gider? 
Hehehe, hayır.????
1 tane sizeof(int) kadar ileri gider. Çünkü int adres bu.

(Arada düşünmüyorum. Parmak egzersizi yapıyorum. Parmak felci geçirdim).

o zaman;

int x[5];
int * isaretci = x;

isaretci++;
*isaretci = 10;

dersem ne olur? x[1] 10 olur. Peki bu nedir?

isaretci = &x[5];

X in son elemanının adresi. Güzel.


for(isaretci = x; isaretci !=(x+5); isaretci++)
*isaretci=rand()%10;

Veya

while (isaretci != (x+5))
*isaretci++=rand()%10;

Gördüğünüz üzere değişken kullanmadan sırf işaretçi ile dizilerin tüm elemanına ulaşabiliyorum. Hız sağlıyor bu.

Tabi modern derleyiciler siz değişken de kullansanız kodu bozup bu hale getirebilir.

Şimdi başka bir muhabbete bakalım.


a ile b değerini bir fonksiyona geçirmek istiyorum ve bu fonksiyon a yı b, b yi d a yapsın. 

int swap(int i, int j){}
dersem
swap(a,b) işleminde fonksiyona a ve b gönderilir ama kopyalanır. i ve j anın kopyası olur.

Şunun gibi:

int a, b, i, j;

/**/

i=a;
j=b;

/* İ ile j yi değiştiren kod */

Ee burada a ve b değişmedi.

işte bu durumda fonksiyona değişkenlerin adreslerimi göndermeliyiz.

swap (int *a1, int *a2);

Başka nerede kullanıyoruz? Ne dedik. Fonksiyoma adres geçilmez ise kopyalanır.

struct A{
/*bir sürü değişken*/
}

struct A d;

eğer d yi ben fonksiyona direk geçirirsem. Kopyalama yapılır ve hem sıkıntılı şeyler çıkabilir hem de yavaş olur hissetmezseniz de.

f(struct A * adres); mantıklıdır.
f(&d); güzeldir.


Bir de dinamik bellek için işaretçiler kullanılır.

Algoritma olarak bazen biz bir alan tahsis etmek ve onu yeri geldiğinde free etmek isteriz. Bu alanı tutacak şeyler ve içeriğine erişmemizi ssğlayacak şeyler işaretçilerdir.

int * x = malloc(3*sizeof(int));

//Dnjdjf

free(x);

Yorumlar (0)

İçerik Hakkında Yorum Yapın