C++ Memory Management 1

Kerim Fırat
4 min readJan 30, 2022

Memory management konusu C++’da karmaşık bir şekil ile karşımıza çıkar.
Özellikle daha üst seviye programlama dilleri ile çalışıyorsak,bu karmaşıklık bizi tekrar tekrar C++’ın en temel konularından biri olan değişkenlere(variables) yönlendirir.
Çünkü memory management en temel haliyle değişkenlere bağlıdır ve özellikle C++’da pointer(*) kavramının olmasıyla bu bağımlılık çok daha geniş ve güçlüdür.

Elbette bu yazı dizisinde değişkenler işlenmeyecektir. Ancak C++’da değişkenlerin davranış şekilleri konusunda bilgi sahibi olmamız önemlidir. Dolayısıyla ilk olarak pointer değişkenlerin memory management yönetimi üzerinde nasıl davrandıklarına bakarak ilerleyeceğiz.

Bellek Tahsisi(Memory Allocation)-Değişkenler(Variables)

Bildiğimiz gibi “değişkenler”,bilgisayar belleğindeki depolama alanını temsil eder.Modern programlama dilleri açısından baktığımızda,bir değişkene yalnızca gerektiğinde bellek verilir.Sistem bir değişkenden belleği geri aldığında ise ilgili değişken serbest bırakılır ve bu nedenle artık değerini depolayacak bir alanı kalmaz.Dolayısıyla bir değişken için tahsisinden itibaren serbest bırakılmasına,yani belleğin ondan geri alınmasına kadar geçen süreye “yaşam süresi” denir. Bir değişken serbest bırakıldıktan sonra ilgili değişkenin değerine ulaşmaya çalışmak,veya daha açık haliyle,ilgili değişkeni kullanmaya çalışmak yaygın bir bellek hatasıdır. Modern programlama dilleri bu hataya karşı bir koruma mekanizması sağlar. Ancak pointer söz konusu ise,programcının tahsisi doğru bir şekilde yaptığına emin olması gerekir.

Local Değişkenler Ve Pointer:
Kullandığımız en yagın değişkenler,”Yerel(local) değişkenler” olarak bilinen, fonksiyon içinde ve/veya yine fonksiyon içinde belli bir kapsama alanı(örneğin fonksiyon içinde blok) olan değişkenlerdir.Aşağıda bir fonksiyon içinde yerel bir değişken kullanılmıştır.
Örnek:

Yukarıda tanımlı olan değişken,içinde bulunduğu fonksiyon alanı dışında etkisizdir. Dolayısıyla fonksiyon içine girildiğinde bellek alanı tahsis edilir ve fonksiyon çıkışında bu alan sistem tarafından alınır. Bu bilinen en temel konudur. Buna yönelik aşağıdaki örneği inceleyelim.

Fonksiyon içinde yer alan “x” değişkeni yerel değişken statüsündedir. Ancak aynı zamanda “x” değişkenine ait değer döndürülüyor.
Burada yerel değişkene ait yaşam alanı bitiyor .Ancak fonksiyonun geri dönüş değeri için yerel olmayan,yani global bir değişken oluşturuluyor. Dolayısıyla ilgili değer çağrıldığı yere ulaşıyor.
Main içeriği:

Ancak pointer kullanan bir programcının yazdığın aşağıdaki fonksiyonu inceleyelim.
Örnek:

Fonksiyon basit haliyle bir int değerine ait adresi(!) döndürüyor. Dolayısıyla aşağıda yer alan main kısmı,gelen bu adresi kullanarak “y” değişkenine ait alana erişmeye çalışıyor.
Örnek ve sonuç:

Output:
Segmentation fault

Çıktıya baktığımızda erişebileceğimiz bir adres-alan olmadığı görülüyor.

Soru: Neden böyle oldu?
Cevap: Elbette pointer konusunu iyi bilen programcılar bu soruyu sormayacaktır. Ancak bu kullanımın yaygın olduğunu belirtmekte fayda var.
Bu kullanımda neler olduğuna gelince:
Fonksiyon içinde “y” değişkeni yerel bir değişkendir ve fonksiyon çıkışında bu değişkene ait alan yok ediliyor. Fonksiyon,az sonra yok edilecek bir değişkene ait alanın adresini döndürmeye hazırlanıyor.Fonksiyon,ilgili adresi döndürmeye başladığında,bu bellek alanı çoktan sistem tarafından geri alınmıştır. Sonuç olarak main içinde bu yok edilen adrese erişilmeye çalışılıyor.

Manuel Bellek Yönetimi

Yukarıdaki bölümde işlediğimiz örneği,otomatik bellek yönetimin bir parçası olarak verebiliriz. Bu bölümde ise manuel olarak bellek tahsisi ve iade süreçlerini işleyeceğiz. Yani bellek tahsisi ve belleği sisteme iade etmek tamamen ve tamamen programcının kontrolünde ve sorumluluğundadır.

Öncelikle bir bellek alanı tahsis edebilmemiz için,minimum olarak tahsis edilecek bellek miktarı kadar sistemde belleğe sahip olmak gerekir. C dili bir nesneye bellek tahsis işlemi için “malloc” fonksiyonu,C++ ise “new” operatörünü kulanır.

Bir nesnesnin ne zaman oluşturulması çok önemli olmasa da,bunun tersi hayati derecede önemlidir. Yani tahsis edilen belleği sisteme geri iade etmek(ki burda manuel bellek yöntiminden bahsediyoruz) kesin olarka gereklidir. Aksi halde tüm sistem ve uygulamalar için bellek problemi ortaya çıkacaktır.
Bellek iade işlemi için C “free()” fonksiyonunu,C++ ile “delete” operatorünü kullanır.

NOT: Elbette “malloc” ve “new” bellek ayırma iş için tek yöntem değildir. Ayrıca “malloc” ve “free()” C++ için de kullanılabilir. Özellikle bu yazının ileriki serilerinde PERFORMANS-HIZ konularını işlerken,C++’da bellek tahsisi ve iade sürecinde ince detaylar ve farklı kullanımlar göreceğiz.

C/C++’da manuel bellek yönetimi bize tam kontrolü ve performansın yanı sıra,ayrıca hayati tehlikeleri de beraberinde verir-getirir.
Bunlara kısaca değinelim.

Memory Leaks(Bellek Sızıntıları):

İhtiyaç halinde tahsis edilen-ayrılan bellek,artık ihtiyaç duyulmadığında yeniden kullanıma iade edilmemesi “bellek sızıntısını” meydana getirir.
Çünkü manuel olarak tahsisi yapılmış bir bellek iade işlemi tamamen programcıya aittir. Eğer programcı bunu unutursa,başka bir yapı tarafından bu işlem yapılmayacaktır ve dolayısıyla sistemde kullanılacak bellek kalmayabilir. Bu durum çok farklı sonuçlarla sonuçlanabilir. Ayrıca normal hata ayıklayıcılarla bu sızıntıları bulmak oldukça zordur. Bellek izleme konularında yine derleyiciye ait özellikler ve araçlar vardır. Bunlardan yararlanılabilir.

Buffer Overruns(Tampon Taşmaları):

Manuel olarak bir nesneye bellek alanı oluşturmak istendiğinde,nesne için bellek miktarı oluşturma sürecinde belirtilir.Bu miktar belirli bir sınır olarak anılır.Ancak ayrılan bellek sınırları dışında başka bir bellek alanına yazıldığında arabellek taşmaları meydana gelir.Diğer bir deyişle “veri bozulması” oluşur. Bu hayati derecede kötü ve istenmeyen bir durumdur.Ayrıca bu tür hataları bulmak oldukça zordur. Ve genellikle programımız garip davranışlarda bulunduğunda bu tür hatalardan şüpheleniriz. Aşağıda verilen basit kod parçası “Buffer Overruns”’a örnektir.

Kod parçasında görüldüğü gibi 10 elemanlı bir dizi’ye 15 eleman atanmaya çalışılıyor. Bir diğer husus ise yapılan bu hatanın güvenlik tarafında da hayati tehlike oluşturmasıdır. Ana konuyu dağıtmamak için güvenlik tarafına girmeyeceğim.

Uninitialized Memory(Başlatılmamış Bellek):

Başlatılmamış bellek anlamı,tahsis edilen ancak değer verilmeyen bir alan demektir. C/C++ bunu yapmamıza izin verir.Dolayısıyla başlatılmamış bir alan okumaya çalışmak yapılan yaygın hatalardan birisidir.

Incorrect Memory Management(Yanlış Bellek Yönetimi):

Yanlış bellek yönetimine örnek olarak “free()” veya “delete” operatorünü birden fazla çağırmak verilebilir. Veya,serbest bırakılan-iade edilen alana erişmek verilebilir. Veya,hiç tahsis edilmemiş bir bellek alanını iade etmeye çalışmak verilebilir. Bu bağlamda aşağıdaki örnek incelenebilir.

Output:
free(): invalid pointer

Eğer free() yerine “delete” kullanmış olsaydık,output aşağıdaki gibi olacaktı.
munmap_chunk(): invalid pointer

C++ Memory Management” başlığıyla oluşturduğum bu yazı serisi ileri düzey konularla devam edecektir. İleriki yazılarda özellikle yer aldığım “Open Source” projeler üzerindeki çalışmalarımdan örnek kod parçaları vererek, geliştirmeler yaparak memory management sonuçlarını gözlemleyeceğiz.

KAYNAKLAR:
https://www.kernel.org/doc/html/latest/admin-guide/mm/index.html
https://www.bogotobogo.com/
https://openjdk.java.net/groups/hotspot/docs/StorageManagement.html

-end of

--

--

Kerim Fırat
Kerim Fırat

Written by Kerim Fırat

Senior Android Platform(AOSP,AAOS) Architect,Open Source Contributor | Turkey Java User Group Vice Chairman | Author

No responses yet