Gömülü C – 13 : İşaretçiler

Tekrardan merhabalar. Bu gün C dilinde pointer olarak adlandırılan konu üzerine bir yazı yazmaya çalışacağım. Yine diğer yazılarda ufaktan da olsa bahsettiğim işaretçiler(pointers) hakkında fark yaratabileceğini  düşündüğüm bilgileri paylaşmaya çalışacağım.

Epeyce bir süredir, ne yazık ki yeni bir yazı yayımlayamadım. Ülkemizde yaşanan çok çok üzücü olaylar, hayatın dönem dönem hepimizi vuran yoğunluğu gibi sebeplerle sessiz bir dönem geçirdim. Ancak ne olursa olsun, başladığımız işleri bitirmekte fayda var. Bu vesileyle bu yazımın temasını “vicdan” olarak belirliyorum.

C dilinin belki de en zor konusudur işaretçiler (pointers). Nedendir hikmeti tam bilinmez, bir türlü açık seçik açıklanamamıştır. Benim yorumum şu; işaretçiler çoğu zaman basit kurgulanmış bir yapı olmadığından, anlaması da çok basit değil. Bu yorumuma katılan, katılmayan olabilir ancak sebeplerimi kendimce sıralayıp ardından olayı açık şekilde anlatmaya çalışacağım. Zira olayın aslı oldukça basit.

İşaretçiler belleğe kestirme yollardan erişmeyi sağlar diyebiliriz. İşaretçiler, türlü potansiyel sorunları beraberinde getirdiğinden bazı dillerde kullanımı yasaklanmıştır. Örneğin C#’da pointer kullanımını açmak için kodu güvenilmez (unsafe) yazdığınızı kabul etmeniz beklenmektedir. Yüksek seviyeli dillerde belleğe doğrudan erişim hakikaten güvenilmez kodlar doğurabilir ancak C gibi orta seviyeli bir dilde işaretçi kullanarak bellek erişimi yapmak, doğru tasarım kalıpları ve prensipler izlendiğinde oldukça verimli olabilir. Bizim de niyetimiz, işaretçileri doğru kullanarak C dilinin güçlü olan bu yanına hükmedebilmek bu sayede de güzel kodlar yazabilmek. Sırf işaretçiler risk unsurları yaratabilir diye, onlara kem gözlerle bakmak, onları kötü kullanmak vicdansızlık olur diye düşünüyorum.

Önemli Nokta
İşaretçilerin temel işlevi: bellekteki bir adres içindeki bir dataya erişim (* operatörü ile) ya da bir değişkenin bellekteki adresine erişim (& operatörü ile) olduğundan önce bir bellek hususunu vicdanların önüne sersek iyi olur diye düşünüyorum.

Bellek Hakkında

Bildiğiniz üzere bellek dediğimiz esasen fiziksel bir bilgi deposu oluyor. Bu zımbırtının veriyi saklama biçimine göre iki temel çeşidi var. Birincisi Volatile Memory denilen “Geçici Hafıza”. Birisi de Non-Volatile Memory(NVM) denilen “Kalıcı Hafıza”. Efendim ne göre geçici ya da kalıcı? Elektrik gittiğinde gösterdiği davranışa göre. Elektriği kessek  (ya da cihaza hard reset atsak) dahi, geri verdiğimizde veriler saklanıyorsa NVM, saklanmıyorsa VM. Misal RAM bir VM’dir yani geçici bellektir. Hard disk veya flash memory ise NVM yani kalıcı bellektir.

Pointer (İşaretçi) Nedir?

Biz işaretçiler ile hem kalıcı bellekteki hem de geçici bellekteki verilere erişebiliyoruz. Çok önemli olduğu için tekrar edeceğim. Bir işaretçi iki operatör ile ifade edilebilir.

 

Arkadaşlar C dilinde her şey bir değişken gibi değerlendirilebilir demiştik. Pointer da benzer muameleye tabi olduğundan, onun da bir tipi vardır. Örneğin

yukarıdaki dataPtr’nin tipi uint8_t* olduğundan, bu işaretçi gösterdiği bellek alanındaki bilgiyi uint8_t olarak yorumlayacaktır. Benzer bir yorum farklı veri tipleri için de yapılabilir. Misal float* tipindeki bir değişken, göstereceği adres içindeki veriyi float olarak yorumlayacaktır. Bu bilgiyi daha detaylıca açıklayacağım.

İşaretçileri anlatmanın en iyi yolu örnekler üzerinden gitmektir. Öyleyse aşağıdaki örnek üzerinden başlayalım.

Bir değişken tanımlandığında, ona uygun bir bellek gözü (ya da bellek gözleri) tahsis edilir. Hangi bellek olacağı işletim sisteminin ve derleme ortamının takdirindedir. Yukarıdaki değişkenler farzı misal 0x2015 adresinden başlayarak yerleşsin.

Şimdi kodun bir yerlerinde C dilinde şunları dersek:

Bunların insan dilinde okunuşu şöyle olur: “num değişkeninin değeri 42’dir. ptr işaretçisi ise num değişkeninin adresine eşittir.”

Tanımlandığında num değişkeni 0x2014 numaralı bellek gözünde yer almaktaydı hatırlayın. Buna göre ptr=0x2014 olması gerekir çünkü num değişkeninin adresi budur. Öteyandan ptr değişkeninin adresi 0x2018 olduğundan, ptr’nin değeri bu adreste saklanır. Buna göre 0x2014 numaralı bellek gözünde 42 yazacak, 0x2018 numaralı bellek gözünde ise 0x2014 yazacaktır. Haydi azıcık zahmet edip çizelim.

 

Şimdi bu ibretlik bilgiyi sınayabileceğiniz tam bir kodu paylaşalım.

Efendim bu kodun benim bilgisayarımdaki çıktısı şöyle:

num’un adresi: 22fe4c
ptr’nin degeri: 22fe4c
num’un degeri: 42
ptr’gosterdigi deger: 42
ptr’nin adresi: 22fe40

——————————–
Process exited with return value 0
Press any key to continue . . .

  • Gördüğünüz gibi, num’un adresi hakikaten ptr’nin degerine eşit.
  • Ptr’nin gösterdigi deger hakikaten num’un degerine eşit.
  • Ve hakikaten ptr, bir degisken olarak bellekte saklaniyor ve adresi num’un adresinden farklı.

Mevzuyu açık seçik gösterdik diye düşünüyorum. Şimdi benzer bir olayı diziler üzerinde anlatalım ki başka püf noktaları görelim.

Öncelikle kodu okuyarak anlamaya çalışınız.

Şimdi bu çıktıları teker teker yorumlayalım. Ancak önce dizinin ve işaretçilerin belleğe nasıl yerleştiğini farz-ı misal bir açıkalayalım.

Bu kodun benim bilgisayarımdaki çıktısı aşağıdaki gibi oluyor.

name’in adresi: 22fe40
p8’in degeri: 22fe40
p32’nin degeri: 22fe40
p8’in gosterdigi deger: 79 [O] p32’nin gosterdigi deger: 1701280335 [65677a4f] p8’in adresi: 22fe38
p32’nin adresi: 22fe30

isaretci degerleri arttiktan sonra!!!

p8’in degeri: 22fe41
p32’nin degeri: 22fe44
p8’in gosterdigi deger: 122 [z] p32’nin gosterdigi deger: 0 [0] p8’in adresi: 22fe38
p32’nin adresi: 22fe30

——————————–
Process exited with return value 0
Press any key to continue . . .

Şimdi bu çıktıları tek tek yorumlayıp ibretleri beynimize indirelim.

“name” ile tanımlanan dizinin başlangıç adresini benim bilgisayarım 0x22fe40 olarak atamış. p8 ve p32 işaretçilerine name dizisinin başlangıç adresini atadığımdan, hakikaten bu iki isaretcinin degeri name dizisinin baslangıç adresi yani 0x22fe40 olmuş. Buraya kadar güzel, zaten konuştuğumuz şeyler.

p8’in gösterdiği değer ‘O’ karakteri yani 79 sayısı olmuş. Bu normal çünkü p8’in değeri zaten name dizisinin başlangıç adresi idi ve o adresin içindeki verinin int8 olarak anlamlandırılması hakikaten ‘O’ karakteri.

p32’nin durumu biraz daha karmaşık duruyor ama aslında olay çok basit. Big endian little endian olayını hatırlarsınız. Ascii table konusunu da konuşmuştuk onu da aklımızın bir köşesinde tutalım. Ve sihirli sayıyı analiz edelim. 0x65677a4f. 0x4F aslında ‘O’ karakteri [bkz: ASCII table]. 0x7a=’z’, 0x67=’g’, 0x65=’e’. Aaaa, aslında bu bizim name dizisindeki ismin int32_t olarak yorumlanmış hali. İşte işaretçinin tipi burada devreye giriyor. Aynı bellekteki bilgi int32_t olarak (yani 4 byte) işaret ediliyor. Her şey şimdi aydınlandı.

p8++ dediğimizde basitçe p8’in değeri kendi cinsinden artırılıyor. Peki  p8 neye eşitti? name dizisinin başlangıç adresine. int8_t tipi 1 byte olduğundan bu p8’in yeni değeri name dizisinin başlangıç adresinin 1 fazlası oldu.

p32++ dediğimizde basitçe p8’in değeri kendi cinsinden artırılıyor. Peki  p32 neye eşitti? name dizisinin başlangıç adresine. int32_t tipi 4 byte olduğundan bu p32’nin yeni değeri name dizisinin başlangıç adresinin 4 fazlası oldu.

Gerisini yorumlamayı size bırakıyorum. Eksik bir nokta olursa çekinmeden sorabilirsiniz, paylaşabilirsiniz.

Bu arada & ile * birbirlerinin tersidir farketmişsinizdir. Örneğin

 

dediğimiz nane aslında p8’nin kendisidir çünkü * ile & birbirini götürür. Yine

 

nanesi de p8’in kendisidir aynı sebepten.

Sizlere nacizane tavsiyem işaretçilerle mümkün mertebe oynamanız.  İşaretçilerin temeli budur. Bir dahaki yazıda uç noktalara değineceğiz.

Yazıları beğendiyseniz eğer,  faydalanabilecek arkadaşlarınızla da paylaşabilirseniz sevinirim.

Şimdi devam…

Önceki Sayfa   Sonraki Sayfa

 

2 thoughts on “Gömülü C – 13 : İşaretçiler

  1. ali

    Hocam ben şurayı anlayamadım..
    Pointer değeri artırıldıktan sonra
    printf(“p32’nin gosterdigi deger: %d [%x] rn”, *p32, *p32); kodu için
    p32’nin gosterdigi deger: 0 [0] denilmiş…
    Normalde Z nin asci kodu 0x7a=’z’ değil mi ??
    Burada bir yanlışlık var galiba..

    1. Merhaba Ali Bey,

      p32’nin tipi uint32_t olduğundan, ++ operatörü adresi 4 byte birden artırır. Bu sebeple bir byte sonraki ‘z’ karakteri yerine, 4 byte sonraki ‘\0’ karakteri işaret edilmiş olur. Kodu çalıştırdığınızda da böyle olduğunu görebilirsiniz. Netice itibariyle bir yanlışlık yok 🙂

Leave a Reply