Nedir bu Memory? (Stack-Heap-Memory Leak-Memory Management)

Emre Karaaslan
5 min readApr 10, 2020

Merhabalar..

Özellikle C/C++ programlama dilini kullananların sıklıkla karşılaştığı, çokça konuştuğu ve tabiri caizse “İyi yazılımcıyı hafıza yönetiminden anlarsın” dediği bir konuya değinmeye çalışacağım.

Evet, bu konunun adı “Memory Management”

Bir şeyi yönetmek için önce onu tanımalı, anlamalı daha sonra yönetmeye çalışmalıyız. Programlarımızda çalışan hafıza için de bu durum tam olarak böyledir. O yüzden gelin öncelikle “memory” yi tanımaya çalışalım.

Hafızayı temelde ikiye ayırıyoruz. İşte o mucizevi iki kelime:

STACK - HEAP

Öncelikle ikisi arasındaki en temel fark; Stack Memory’deki değerler son giren ilk çıkar mantığına göre tutulurken, Heap Memory’de bu durum rastegeledir(random). Programlarımızda bu iki belleği birbirinden olabildiğince ayırırız

Stack Memory, işlemcilerin register bilgilerinin tutulduğu yerdir. Burada programınızla ilgili bilgiler (örneğin; lokal değişkenler, referans değişkenler vs) yer almaktadır. Bu memory, geliştirici tarafından değil, compiler tarafından yönetilir. Stack’teki bilgiler kodunuzun derleme aşamasında, direk bellek içine yerleştirildiği için erişimi oldukça hızlıdır.

Heap Memory, bellek üzerinde yer tahsisi yapılan belli bir bölümdür. Bu yer, bellek üzerinde “malloc” fonksiyonu aracılığıyla tahsis edilir ve heap üzerinde allocate edilen(yer tahsisi yapılan) bellek “free” lenerek tekrar kullanım için serbest bırakılır. Heap’teki bellek kullanımı compiler tarafından değil, geliştiriciler tarafından kontrol edilir. Karmaşık programlar oluştururken, genellikle büyük bir bellek alanına ihtiyaç duyarız. Bu durumda Heap Memory kullanırız. Heap üzerinde allocate ettiğimiz bellek operasyonuna “dynamic memory allocation” adı verilir.

Stack Memory’yi daha yakından inceleyelim :

Biz geliştiriciler için çok önemli olmasa da, programımız işlemci üzerinde çalışabilmek için sürekli stack belleğini arar. Her lokal değişken ve çağrılan her fonksiyon buraya gider. Genellikle istemeden de olsa stack üzerinde birçok hatalarla karşılaşabiliriz. Sıklıkla karşılaştığımız stack buffer overflow ve incorrect memory’ye erişmeye çalışmak bu hatalardan bazılarıdır.

Peki ama nasıl çalışıyor abi bu stack?

Öncelikle stack bir LIFO (Last In First Out) data structures’dır. Somutlaştırmak adına LIFO’yu şöyle düşünebiliriz:

İçine kitapların yalnızca üst üste yerleşebildiği kapağı açık boş bir kutu düşünün. Örnek olarak, kutuya üst üste 5 kitap koydunuz. Kutudan çıkaracağınız ilk kitap 5. koyduğunuz kitap olacaktır. Yani son giren 5. kitap ilk çıkacak kitaptır. LIFO dediğimiz olay da tam olarak budur. Aşağıdaki görselle de bunu somutlaştırabilirsiniz.

LIFO

Bu yapıyı kullanan bellek, iki basit işlem ile süreci yönetmektedir. Hazırsak hayati iki kelime daha geliyor:

Push & Pop

Bu iki işlem birbirlerinin tam olarak tersidir. Push, programdan gelen bir değeri stack’in üzerine eklerken, pop is en yüksek değeri stack’ten çıkarır, boşaltır.

Push-Pop Operations

Yukarıdaki gibi bir pop-push operasyonunda, belleğe girip çıkacak değerlerin takibini yapabilmek için Stack Pointer adı verilen özel bir işlemci register’ı vardır. Geliştiriciler, lokal bir değişken veya fonksiyonun dönüş adresi gibi belleğe sürekli bişeyler kaydetmeye çalışır. Bu senaryoda stack bir değeri pushlar ve pointer ı yukarı taşır. Program fonksiyondan her çıktığında ya da tanımlanan değişkenler kaybolduğunda, stack memory bu değerleri pop eder. Böylece belleğin daha verimli kullanılmasını sağlar.

Şimdi gelin kod parçacığı üzerinde bu duruma bir göz atalım:

Yukarıdaki örnekte program createArr fonksiyonunu çağırdığında Stack aşağıdakileri uygulamaya çalışır:

  • Fonksiyonun return adresini kaydeder.
  • Stack memory’de “arr” isiminde bir dizi yaratılır ve bunu döndürür. (Burada dizi, içindeki tüm bilgilerle birlikte bir stack üzerindeki bir bellek lokasyonuna işaret eden pointerdır.)
  • Ancak programda malloc kullanılmadığı için, üretilen dizi direkt olarak stack memory’ye kaydedilir.

Pointer, fonksiyon döndükten sonra, stack üzerinde herhangi bir kontrole sahip değildir. Bu nedenle compiler, bilgileri stackten boşaltır (pop operasyonu) ve gerektiği gibi kullanır. Fakat fonksiyondan döndükten sonra, dizinin içeriğini doldurmaya çalıştığımızda bellek bozulur (memory corruption) ve mis gibi bir “segfault” yeriz.

Biraz da Heap Memory’ye bakalım:

Stack’in tersine, geliştiriciler tarafından yönetilen RAM’deki memory bölgesidir. Heap üzerinde bellek ayırabilmek için C dilinin “stdlib” kütüphanesiyle birlikte yerleşik olarak gelen “malloc”, “calloc” gibi fonksiyonlar kullanılır. Ayrılan bellek, scope dışına çıkıldığı zaman kullanılmaya devam eder. Bunu önlemek için de yine aynı kütüphane ile birlikte gelen “free” fonksiyonu kullanılarak ayrılan hafıza, heap memory tarafından yeniden kullanılabilmek için serbest bırakılır. Heap memory’nin okunması ve yazılması stack memory’ye göre daha yavaştır, çünkü heap üzerindeki belleğe erişmek için pointer kullanılmak zorundadır.

malloc” ve “free” fonksiyonlarının sistem üzerindeki akışlarını inceleyecek olursak, “malloc” gerekli olan memory miktarını CPU’dan ister ve başlangıç adresine bir pointer döndürür. “free” ise CPU’dan istediğimiz belleğin artık gerekli olmadığını ve diğer görevler için kullanılabileceğini söyler.

İşlemci, geliştiricilerin isteklerini yanıtsız bırakamaz. Yani yukarıdaki iki fonksiyonla bu belleği yönetmek insana bağlıdır ve bu da kötü yönetildiğinde kocaman bir probleme yol açar. Bunun adı “Memory Leak” …

Memory Leak, program sona erdiğinde veya pointerların lokasyonları kaybolduğunda ortaya çıkan bir problemdir. Geliştiriciler tarafından allocate edilen, fakat daha sonrasında “free”lenmeyen memory bu probleme sebep olur. Programın kullanması gerekenden daha fazla memory kullandığı anlamına gelmektedir.

Yukarıdaki kod parçacığının doğru olmayan kullanım senaryosunda, ilk satırda 5 * 4 = 20 byte lık bir yer ayrılır (int = 4 byte). Ancak hafıza boşaltılmadığı için bir sonraki satırda yeniden 5 * 4 = 20 byte lık bir alan daha ayrılır. Bu da toplamda 40 byte lık bir memory israfına yol açar.

Heap memory’yi yönetmek, programların belleğini verimli kullanabilmek adına oldukça önemlidir. Dikkatli olmak gerekir. Örnek olarak malloc ile yer ayrılmış bir memory’yi freeledikten sonra erişmeye çalışmak Stack’te olduğu gibi segmentation fault a sebep olur.

Struct’larda Heap Kullanımı

Struct, C/C++ dilinde en çok kullandığımız yapılardan birisidir. Bunu heap memory üzerinde kullanırken yapılan en büyük hatalardan biri de “free”lemektir. Struct’ın içinde pointerlara memory ayrılmadığı sürece free lemek doğru bir kullanımdır. Fakat eğer strcut içindeki pointerlara bellek ayrıldıysa önce bu ayrılan bellek, daha sonra struct’ın tamamı freelenmelidir.

Şahsi olarak struct kullanırken 2 adet fonksiyonu kullanmaya kendimi zorunlu tutuyorum. Constructor ve Deconstructor. Bu iki fonksiyon mallocları kullandığım ve free lediğim fonksiyonlarımdır. Bu sayede kod takibini daha kolay yapabilir, memory leak i öneleyebilirsiniz:

Ayrıca Memory Management için Valgrind adında bir tool kullanarak işlerinizi kolaylaştırabilirsiniz.

Şimdilik bu kadar. Herhangi bir durumda bana Linkedin üzerinden ulaşmakta çekinmeyin.

Sağlıklı, mutlu, huzurlu günler dilerim…

--

--