C++ Pointers (İşaretçiler)

Bu yazıda, C ve C++ dillerinde kullanılan pointerları yani Türkçesiyle işaretçileri inceleyip temel mantığını anlayacağız.

Ertan Özdemir
ROYTO
Published in
5 min readApr 9, 2021

--

C ve C++ gibi makine diline yakın dillerde işaretçiler yardımıyla memory’e erişmek mümkün hale gelebiliyor. Daha gelişmiş programlama dillerinde örneğin Python, JavaScript gibi, bu işlemleri sizin yerinize yapan daha farklı yapılar bulunuyor. Bu sebeple daha önce programlama kariyerine Python veya JavaScript gibi gelişmiş dillerde başlayan geliştiricilerin, C++ dilinde yazılım geliştirmeye başladığı anda karşılaştığı işaretçi kavramı oldukça kafa kurcalayıcı olabiliyor. Hatta bazı geliştiriciler için bir o kadar lanet bir dil haline bile gelebiliyor 😏. Hepimiz aynı yollardan geçiyoruz fakat bu konu üzerinde kafa patlattıkça işlerin bir o kadar kolaylaştığı da aşikâr. Bu yazının sonunda işaretçilerin nasıl kullanıldığı ve çalışma mantığı hakkında bilgiler edineceksiniz. Hadi başlayalım!

Değişkenlerin Adreslerini Bulalım

Bir programlama dilinde değişken tanımladığınız anda o değişken için bellek üzerinde uygun bir yer ayrılır. Aynı evimizin bir adresi olduğu gibi bellek üzerinde ki her değişkenin de bir adresi vardır. Hemen Leonardo Da Vinci çizim yeteneklerimi kullanarak bunu görsel hale getirelim;

Değişkenler Bellek Üzerinde

C++’ta “int a = 15” ve “int b = 112” olacak şekilde iki tane farklı değişken oluşturduk. Yukarıda göreceğiniz üzere bu iki değişken için bellekte iki farklı yer ayrıldı ve değişkenler ayrılan bu iki yere yerleştirildi. Buraya kadar bir sorun olmadığını düşünüyorum. Resme dikkatli bakarsanız “0x7ffcb6e05ffc” ve “0x7fff8234893c“ gibi iki tane veri dikkatinizi çekecektir. Bu veriler yukarıda da bahsettiğimiz gibi değişkenlerimizin bellek üzerinde ki adresleridir. Peki ama bu adreslere nasıl ulaşacağız? C ve C++’ta tanımladığımız değişkenlerin bellek adreslerine erişmek için (&) notasyonu kullanılır. Aşağıdaki kodu inceleyelim;

Değişkenin Bellek Üzerindeki Adresini Öğrenmek

İlk olarak a adında bir değişken oluşturduk ve bu değişkene 15 değerini atadık. Daha sonra bu 15 değerinin bellek üzerinde hangi adreste tutulduğunu öğrenmek için &a ifadesini cout yardımıyla ekrana bastırarak öğrenmiş olduk.

Demek ki “&” notasyonu, bize değişkenimizin bellek üzerindeki adresini veriyormuş. Bunu da “&<degisken_ismi>” şeklinde kullanmamız gerekiyormuş.

İşaretçileri (Pointers) Anlayalım

Yukarıda değişkenlerimizin bellekteki adreslerine nasıl ulaşacağımızı öğrenmiştik. Şimdi ise bunu işaretçilerle nasıl kullanabiliriz bir de ona bakalım.

İşaretçiler için şöyle bir tanımlama yaparsak yanlış olmayacaktır: İşaretçiler, değişkenlerin bellekteki adreslerini tutmaya yarayan bir tür özelleşmiş değişkenlerdir. Bu tanım size biraz karışık gelmiş olabilir fakat endişelenmeye hiç gerek yok bir örnek üzerinden daha net anlayacağınızdan eminim;

Pointer’a ilk adım

“Değişken adreslerini bulalım” kısmında yazdığımız kodun çok benzeri bir kod. Ufak tefek farklılıklar var, hemen onlardan bahsedelim. “int *ptr” diye bir değişken tanımlanmış fakat bu değişkenin hemen solunda bir asteriks (*) sembolü kullanılmış. Bu sembol ptr adlı değişkenin aslında bir pointer (işaretçi) olduğunu belirtiyor. Yani buradan ne anlamamız gereken, ptr adlı değişkenin bir bellek adresi tutması gerektiğidir. Çünkü hatırlasanız, tanımımızda pointerların bellek adresi tuttuğundan bahsetmiştik.

E, o zaman biz de ona tutması için bir başka bir değişkenin bellek adresini verelim. Unutmadıysak eğer bir değişkenin bellek adresini - &- notasyou sayesinde edinebiliyorduk. 10. satırda pointer’ımıza bellek adresini ptr=&a yazarak atadık. Buraya kadar yapılan işlemler, bir değişkene değer atamaktan farksız.

ptr = &a demek, ptr = 0x7ffd1a6b0944 demektir diye düşünebilirsiniz.

Yukarıdaki işlemleri tasvir ettiğim görseli incelerseniz ne demek istediğimi çok daha iyi anlayacaksınız. “ptr” adlı pointer, “a” değişkeninin adresini tutuyor.

Ufak bir not bırakmak istiyorum; asteriks’i (*), tanımladığınız değişkenin soluna koymanız gerekir (int *ptr). Aksi takdirde derleyici * işaretini çarpma işlemi olarak algılayabilir. Ayrıca pointer türünde olmayan bir değişkene başka bir değişkenin adresini atayamazsınız. Bu yüzden programınız hata verebilir. Bunlara ek olarak pointer’ınızın değişken türü, adres ataması yapacağınız değişkeninizle aynı olmalıdır. Aynı değilse derleyici yine hata verecektir. Evet, buraya kadar bir sorun olmadığını varsayarak devam edelim.

Pointerlar sadece başka bir değişkenin adresini tutmaktan ziyade bu tutulan adrese de erişebilir. Bu yüzdendir ki Türkçe’ye gösterici veya işaretçi olarak çevrilmiştir. Aşağıda ki kodu inceleyelim;

*ptr kullanarak adrese erişim sağlamak

Yukarıdaki koddan çıkaracağımız sonuç;

  1. cout<< ptr : Tutulan değişkenin adresini gösterir (0x7ffd33ae6ea4).
  2. cout<<*ptr : Tutulan değişkenin adresindekini gösterir (12).

Dolayısıyla, 11. satırda ptr’nin yanında kullandığımız * işareti adresteki veriye eriş anlamını taşımaktadır.

Peki, pointerlar ile değişkenleri değiştirmek mümkün mü?

Pek tabi mümkün. Pointerlar ile adresteki verilere erişebildiğiniz için onları istediğinizde değiştirebilirsiniz.

Pointer ile değişkeni değiştirmek

İlk olarak 10. satırda ptr değişkenine a’nın adresini atadık, daha sonra 11. satırda * yardımıyla ptr’de tutulan a değişkeninin adresine gittik ve burada bulunan 12 değerini 38 ile değiştirdik.

Burada unutulmaması gereken husus, * işaretini kullanmadan ptr değişkenine 38'i atamaya çalışsaydık hata alacaktık. Çünkü ptr değişkeni bellek üzerindeki bir adresi tutuyor ve biz buradaki adresi 38 ile değiştirmeye çalışmış olurduk. Ancak “ *ptr = 38 “ şeklinde atama işlemi yaptığımız zaman ptr’de tutulan adrese git buradaki değere 38'i ata demiş oluruz. Bu konu, pointer kavramıyla ilk defa karşılaşan geliştiriciler için oldukça kafa kurcalayıcı olabilir. Bu sebeple iki kere üzerinden geçeyim dedim.

Pointerlar’a Pointer Yardımıyla Erişim

Pointerların aslında bir değişken olduğundan bahsetmiştik, bu yüzden hepsinin bellek üzerinde birer adresi vardır. Bizim başka pointerlar kullanarak pointerlara ulaşmamız mümkündür. Yani bir nevi pointer’ın pointer’ı gibi düşünebilirsiniz.

Pointer’ın pointer’ı

Yukarıda ki örnek biraz karışık olduğu için adım adım açıklıyalım;

  • 7. ve 8. satırda *ptr ve **ptr2 adında iki tane pointer oluşturduk. ptr2'nin yanında bulunan iki tane * işaretinin sebebi başka bir pointer’a işaret etmesidir. Eğer ptr2'ye işaret eden başka bir pointer daha olsaydı üçüncü pointer’ın başına üç tane * işareti koymamız gerekecekti. Sayı bu şekilde artıp giderdi.
  • 11. satırda ptr adlı değişkene, a’nın adresini atadık.
  • 12. satırda ise bu sefer ptr adlı pointer’ın adresini ptr2 adlı pointer’a atadık. Yani pointer’a pointer atamış olduk.
  • 14. satırda ptr adlı pointer’ımızın adresini öğrendik ve 15. satırda ekrana bastırılan ptr2'nin, ptr adlı pointer’ın adresini tuttuğunu çıkan değerlerin aynı olmasından teyit ettik.
  • 17. Satırda ise **ptr2 diyerek ilk * işaretinde ptr2'nin tuttuğu adreste ne var onu görmek istedik, ikinci * işaretinde de tuttuğu adresteki adreste ne var onu görmek istemiş olduk.

Açıkladıkça daha çok karışan bu işlemleri görselleştirelim;

Pointerlar gerçekten C ve C++ deneyimi olmayan geliştiriciler için oldukça kafa kurcalayıcı bir konu. Dolayısıyla başlarda anlamamanız gayet doğal. Anlamak için üzerinde çalışmak gerekiyor. Öğrenme süreci sadece bir yazıyı okumakla değil, onu pratiğe döktüğünüz zaman gerçekleşir. Aksi takdirde burada anlatılan temel konsepti kavrasanız bile pratiğe dökmediğiniz sürece onu öğrendiğinizi kanıtlayamazsınız ve ayrıca ileride unutursunuz. O halde bir an önce kod editörünüzü açın ve yukarıdaki kodları yazarak işe başlayın 😉

--

--