"Enter"a basıp içeriğe geçin

Dinamik Bellek

Dinamik bellek nedir

Daha önce pointer’lardan, operatör aşırı yüklemeden ve array’lerden bahsetmiştik. Şimdi bellekte yer almayı da gösterdikten sonra dinamik bellek kullanımıyla ilgili öğrendiklerimizi ve yeni öğreneceklerimizi kullanarak basit bir int array sınıfı oluşturacağız

Bildiğiniz gibi dizinin adı, aslında ilk adresi gösteren bir pointer idi. Yer alırken de buna benzer bir yapı kullanacağız.

new ile dinamik bellek üstünde yer alma

ptr= new int [size] {}  //ptr, alacağımız alanın başlangıç adresidir. int verimizin tipi, size ise boyutu (alacağımız yer miktarı) olur. new , bellekten yer alan bir anahtar kelimedir. {} ile de tüm değişkenleri 0 olarak ilklendirdik. Yani aslında kendimize adı ptr olan size boyutunda ve tüm elemanları 0 olan integer bir dizi oluşturduk

Eğer dizi değil de tek int kadar yer alacaksak ptr= new int diyebiliriz

 

Heap ve Stack

Dinamik bellek ile ilgili stack ve heap adında 2 kavramdan bahsetmek gerekir. Bunlara daha sonra detaylı değineceğiz ama şimdilik şu kadarını bilsek yeter: Bizim programımız çalıştırıldığında bellekte (ram) bir yer tutar.

Boyutları daha önceden belli olan, yani dinamik olmayan şeyler belleğin stack kısmında tutulur. Stack’e erişim daha hızlı ve kolaydır. Fonksiyon bloğundan çıkıldığında yada program sonlandığında stack’teki veriler otomatik olarak silinir. Derleme zamanı oluşturulur ve boyutu bir daha değiştirilemez.

Kaplayacağı yer çalışma zamanında belli olacak şeyleri ise heap denilen kısımda tutarız. Yani biz program çalışırken bana n boyutunda bir yer tut dersek, n derleme zamanında bilinmediği için heap’te tutulması gerekir. Kısacası dinamik bellekle ilgili şeyler heap’te tutulur. Heap üzerindeki verilere pointer’lar aracılığıyla ulaşırız. Heap üzerindeki verilerin özelliği,çalışma zamanında oluşurlar ve  fonksiyon bloğu bittiğinde, hatta program sonlandığında bile otomatik olarak silinmezler. Bu silme (yani belleği boşaltma) işlemini kendimiz yapmalıyız. Yoksa bellekte gereksiz yer işgal eder ve bazen ciddi bellek sorunlarına yol açabilir.

ptr= new int [size] {}; dediğimizde boyut değeri çalışma zamanında belli olacağı için heap üzerinden yer aldı. Bu yüzden bu yeri delete kullanarak kendimiz silmeliyiz. Sonrasında ptr pointer’ına nullpointer atarsak güzel olur.

destructor: objeleri ortadan kaldırmaya yarar. Normalde derleyici biz yazmasak da yazar ve nesneyi yok eder. Ancak heap’teki verilere dokunmaz. Stack’teki verileri yok eder. eğer dinamik bellek ile uğraşıyorsak heap’teki verileri yok etmek için kendimiz yeni bir destructor tanımlamalıyız.

new ile yer alırken boyut yazmalıyız. Ancak destructorda boyut belirtmemize gerek yoktur.

Köşeli parantezle dizi için yer aldıysak, köşeli parantezle yok etmeliyiz :  delete[] ptr;
Tek yer aldıysak parantez kullanılmaz :  delete ptr;

Kendimiz çalıştırabildiğimiz gibi, program bitince de destructor otomatik çağrılır.

Örnek dinamik bellek kodu

Basit bir konteyner sınıf oluşturduk. Sadece integer değerler tutabiliyor. Bu kodu çok dikkatli inceleyin. Main kısmını birazdan yazacağız

Dinamik bellek ile ilgili bir sınıf yapıyorsak unutmamamız gereken 3 şey vardır. Bunlar copy constructor, destructor ve assignment ( = ) operatörüdür. Neden unutmamamız gerektiğine gelirsek, normalde bunları yazmazsak bile derleyici bizim yerimize bu 3 fonksiyonu da yazar. Ama dinamik bellekle işlem yapıyorsak durum biraz değişir. Artık bu default gelen copy constructor, destructor ve assignment operatörü bizi işimizi görmez. Destructor’u neden kendimiz tanımlamamız gerektiğini söylemiştik. Diğerlerinin de nedenine bakalım

İki çeşit kopyalama vardır

Sığ kopyalama

değişkenlerin içindeki değerler aynen diğer objeye kopyalanır. Temel tiplerde işe yarar. Ama eğer bu örnekteki gibi değişkenin içinde pointer varsa, bu pointer’ları kopyalamaya çalışacaktır. Pointerların içinde adres olduğu için bu adres kopyalanacaktır ve aynı adresi gösteren iki pointer olacaktır. Bu durumda aynı adresi gösterdikleri için birinde yapılan değişiklik diğerini de etkiler. Bu gerçek bir  kopyalama değildir. Örneğimizde copy constructor’un içine arr=dizi2.arr yazsaydık sığ bir kopyalama yapmış olurduk.

Derin kopyalama

Kendimiz copy constructor yazıp, her bir elemanı tek  tek atarsak, bu deep copy olur. Yani gerçekten elemanların değerini kopyalarız. Bu durumda aynı adres üzerinden çalışmak söz konusu olmaz. Bu koddaki copy constructor, dizinin tüm elemanlarını tek tek atadığı için deep copy işlemi yapar.

Assignment operatöründe de aynı problem vardır. derin değil sığ kopyalama yapar. Eğer  heap’ten yer aldıysak yani dinamik bellek işlemi yapıyorsak derin kopyalama yapmak için = operatörünü kendimiz tanımlamalıyız.

Kendini kendine atamanın sıkıntıları da şudur: eğer ilk kontrolü yapmasaydık, boşuna işlem yapmış olacaktık. iki kontrolü de yapmasaydık ise önce delete ile sildiğimiz için atanacak birşey kalmayacaktı. Yani ilk verimiz yok olacaktı ve hata alacaktık. Yani kendine atamanın riski, önce eski verileri silme ihtimali olmasıdır. Bu  bir mantık hatasıdır.

Örnek kodumuza geri dönelim

Deep copy yaptığımız için destructor d dizisini dizi1 dizisine aynen kopyaladı.
Aynı şekilde assigment operatörü de dizi2’yi hatasız şekilde dizi1’e kopyaladı
Program bittiğinde destructorlar otomatik olarak çalıştı

    Bir cevap yazın

    E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir