← Kursa Dön
📄 Text · 15 min

Dinamik Bellek Yönetimi (new/delete)

Şimdiye kadar tanımladığımız tüm değişkenler "otomatik" olarak yönetildi — tanımladın, kullandın, fonksiyon bitince silindi. Ama ya programın çalışırken ne kadar bellek gerektiğini önceden bilmiyorsan? Kullanıcıdan 5 öğrencinin notu mu gelecek, 5000 öğrencininkini mi?

İşte dinamik bellek yönetimi (dynamic memory management) burada devreye girer. Programın çalışma zamanında bellek isteyebilir ve işi bitince geri verebilirsin.


Stack vs Heap Bellek

Bir C++ programı çalışırken bellek ikiye ayrılır: stack ve heap.

Depo Analojisi

Stack, masanın üstü gibidir. Küçük, düzenli, hızlı erişim. Dosyaları üst üste koyarsın, en üsttekini alırsın. Ama masanın yüzeyi sınırlı — çok büyük şeyler sığmaz.

Heap, büyük bir depo gibidir. Devasa, ama düzensiz. İstediğin kadar raf ayırabilirsin, ama nereye koyduğunu not etmen lazım. Yoksa bir daha bulamazsın.

Stack Bellek

  • Fonksiyon çağrılarında otomatik ayrılır ve geri verilir

  • Çok hızlı (sadece bir pointer kaydırma işlemi)

  • Boyut sınırlı (genelde 1-8 MB)

  • Değişken tanımlayınca otomatik kullanılır

void fonksiyon() {
    int x = 42;           // Stack'te
    double y = 3.14;      // Stack'te
    int dizi[100];        // Stack'te (400 byte)
}  // Fonksiyon bitince hepsi otomatik silinir

Heap Bellek

  • Programcı tarafından manuel ayrılır (new) ve geri verilir (delete)

  • Stack'e göre yavaş (işletim sistemi aracılığıyla)

  • Çok büyük (gigabyte'larca kullanılabilir)

  • Fonksiyon bitince otomatik silinmez — sen silmelisin

void fonksiyon() {
    int* ptr = new int(42);  // Heap'te yer ayrıldı
    // ... kullan ...
    delete ptr;               // Manuel olarak sil
}

Karşılaştırma Tablosu

ÖzellikStackHeap
HızÇok hızlıDaha yavaş
BoyutSınırlı (1-8 MB)Çok büyük (GB'larca)
YönetimOtomatikManuel (new/delete)
Yaşam süresiFonksiyon/blok sonuna kadardelete'e kadar
ParçalanmaYokOlabilir (fragmentation)
ErişimSadece tanımlandığı bloktaPointer ile her yerden

new ile Bellek Ayırma

new operatörü heap'te bellek ayırır ve o belleğin adresini (pointer) döndürür.

Tek Değişken Oluşturma

#include <iostream>
using namespace std;

int main() {
    // Heap'te bir int olustur
    int* ptr = new int;
    *ptr = 42;
    cout << "Deger: " << *ptr << endl;

    // Deger vererek olusturma
    int* ptr2 = new int(100);
    cout << "Deger: " << *ptr2 << endl;

    // C++11 surduzlu baslatma
    int* ptr3 = new int{200};
    cout << "Deger: " << *ptr3 << endl;

    // Bellegi geri ver
    delete ptr;
    delete ptr2;
    delete ptr3;

    return 0;
}

Dizi Oluşturma

#include <iostream>
using namespace std;

int main() {
    int boyut;
    cout << "Kac eleman? ";
    cin >> boyut;

    // Heap'te dinamik dizi olustur
    int* dizi = new int[boyut];

    // Degerleri oku
    for (int i = 0; i < boyut; i++) {
        dizi[i] = (i + 1) * 10;
    }

    // Yazdir
    for (int i = 0; i < boyut; i++) {
        cout << dizi[i] << " ";
    }
    cout << endl;

    // Bellegi geri ver
    delete[] dizi;

    return 0;
}

Stack'te int dizi[boyut] yazamazsın (VLA — standart C++'ta yok). Ama new int[boyut] ile istediğin boyutta dizi oluşturabilirsin. İşte dinamik belleğin gücü bu.

💡 İpucu: new int[5]{} yazarsan tüm elemanlar sıfırla başlatılır. new int[5] yazarsan değerler başlatılmamış olur (çöp veri).

int* a = new int[5];     // Baslatilamis — cop deger
int* b = new int[5]{};   // Tumu 0
int* c = new int[5]{1, 2, 3};  // {1, 2, 3, 0, 0}

delete ve delete[]

Heap'te ayırdığın belleği geri vermezsen, o bellek programın sonuna kadar meşgul kalır. Buna memory leak (bellek sızıntısı) denir. delete ve delete[] bu belleği geri verir.

Kural: new ile ayırdıysan delete, new[] ile ayırdıysan delete[]

int* tek = new int(42);
delete tek;          // Tek degisken icin delete

int* dizi = new int[100];
delete[] dizi;       // Dizi icin delete[]

⚠️ Dikkat: new[] ile ayırdığın belleği delete ile (köşeli parantezsiz) silmek undefined behavior'dır. Program çökebilir veya bellek bozulabilir. Her zaman eşleştir: newdelete, new[]delete[].

delete Sonrası Pointer

delete çağırdıktan sonra pointer hâlâ eski adresi tutar — ama o adresteki bellek artık geçerli değil. Buna dangling pointer denir.

#include <iostream>
using namespace std;

int main() {
    int* ptr = new int(42);
    cout << *ptr << endl;  // 42

    delete ptr;
    // ptr hala eski adresi tutuyor — ama o adres artik gecersiz!
    // cout << *ptr;  // Undefined behavior!

    ptr = nullptr;  // Guvenli hale getir
    // Artik nullptr kontrolu yapilabilir

    return 0;
}

İyi bir alışkanlık: delete sonrası pointer'ı nullptr yap. Böylece yanlışlıkla tekrar kullanırsan, en azından kontrol edebilirsin.

Çift delete Tehlikesi

Aynı belleği iki kez silmek de undefined behavior'dır:

int* ptr = new int(42);
delete ptr;
// delete ptr;  // TEHLIKE! Cift delete — undefined behavior

ptr = nullptr;
delete ptr;     // OK — nullptr'i delete etmek guvenlidir (hicbir sey yapmaz)

Memory Leak Nedir?

Memory leak (bellek sızıntısı), ayırdığın belleği geri vermemeye denir. Program çalıştıkça bellek tüketimi artar, sonunda sistem yavaşlar veya çöker.

void kotuFonksiyon() {
    int* ptr = new int[1000];
    // ... bir seyler yap ...
    // delete[] ptr;  // Unuttuk! Memory leak!
}  // ptr yok olur ama heap'teki 1000 int hala orada

Yaygın Memory Leak Senaryoları

1. delete'i unutmak:

void senaryo1() {
    int* p = new int(42);
    // ... return veya exception ile fonksiyondan cikis
    // delete p;  // Buraya ulasilamadi!
}

2. Pointer'ı üzerine yazarak kaybetmek:

void senaryo2() {
    int* p = new int(42);
    p = new int(100);  // Ilk ayrilan bellegin adresi kayboldu!
    delete p;           // Sadece ikinci ayirmayi siler
}

3. Exception durumunda:

void senaryo3() {
    int* p = new int(42);
    riskliFonksiyon();   // Exception firlatirsa...
    delete p;            // ...buraya ulasilamaz!
}

Memory Leak Nasıl Önlenir?

  1. Smart pointer kullan (en iyi çözüm — bir sonraki ders)

  2. Her new için mutlaka bir delete olduğundan emin ol

  3. Erken return'lerden önce temizlik yap

  4. RAII prensibini uygula (resource acquisition is initialization)

  5. Valgrind gibi araçlarla test et

// KOTU
void kotu() {
    int* p = new int[100];
    if (hataVar()) return;  // Leak!
    delete[] p;
}

// DAHA IYI
void dahaiyi() {
    int* p = new int[100];
    if (hataVar()) {
        delete[] p;  // Temizlik yap
        return;
    }
    delete[] p;
}

// EN IYI — smart pointer (sonraki ders)
void eniyi() {
    auto p = make_unique<int[]>(100);
    if (hataVar()) return;  // Otomatik temizlenir!
}

Pratik Örnek: Dinamik Dizi Sınıfı

Basit bir dinamik dizi yönetimi:

#include <iostream>
using namespace std;

int main() {
    int adet;
    cout << "Kac ogrenci? ";
    cin >> adet;

    // Dinamik dizi olustur
    double* notlar = new double[adet];

    // Notlari al
    for (int i = 0; i < adet; i++) {
        cout << "Ogrenci " << (i + 1) << " notu: ";
        cin >> notlar[i];
    }

    // Ortalama hesapla
    double toplam = 0;
    for (int i = 0; i < adet; i++) {
        toplam += notlar[i];
    }
    cout << "Ortalama: " << toplam / adet << endl;

    // Bellegi temizle
    delete[] notlar;
    notlar = nullptr;

    return 0;
}

Bu çalışır, ama modern C++'ta std::vector kullanmak çok daha güvenli ve pratiktir. Dinamik bellek yönetimini anlamak yine de önemli — çünkü vector arka planda tam olarak bunu yapıyor.


Dinamik 2D Dizi

İki boyutlu dinamik dizi oluşturmak biraz daha karmaşık:

#include <iostream>
using namespace std;

int main() {
    int satir = 3, sutun = 4;

    // 2D dizi olustur (pointer'larin pointer'i)
    int** matris = new int*[satir];
    for (int i = 0; i < satir; i++) {
        matris[i] = new int[sutun]{};  // Her satir icin dizi
    }

    // Degerleri ata
    int sayac = 1;
    for (int i = 0; i < satir; i++) {
        for (int j = 0; j < sutun; j++) {
            matris[i][j] = sayac++;
        }
    }

    // Yazdir
    for (int i = 0; i < satir; i++) {
        for (int j = 0; j < sutun; j++) {
            cout << matris[i][j] << "\t";
        }
        cout << endl;
    }

    // Bellegi TERS SIRADA temizle
    for (int i = 0; i < satir; i++) {
        delete[] matris[i];  // Once satirlari sil
    }
    delete[] matris;         // Sonra pointer dizisini sil

    return 0;
}
1	2	3	4
5	6	7	8
9	10	11	12

⚠️ Dikkat: 2D dinamik dizi silme sırası önemli — önce iç dizileri (matris[i]), sonra dış diziyi (matris) sil. Tersini yaparsan iç dizilerin adreslerini kaybedersin → memory leak.


new ile Hata Durumu

Eğer sistem bellek ayıramıyorsa (bellek yetersizse), new varsayılan olarak std::bad_alloc exception'ı fırlatır:

#include <iostream>
#include <new>
using namespace std;

int main() {
    try {
        // Cok buyuk bellek iste
        int* dev = new int[999999999999];
        delete[] dev;
    } catch (const bad_alloc& e) {
        cout << "Bellek ayrilamadi: " << e.what() << endl;
    }

    // Alternatif: nothrow versiyonu
    int* ptr = new(nothrow) int[999999999999];
    if (ptr == nullptr) {
        cout << "Bellek ayrilamadi (nullptr dondu)" << endl;
    } else {
        delete[] ptr;
    }

    return 0;
}

new(nothrow) exception fırlatmak yerine nullptr döndürür. C tarzı programlamaya daha yakın bir yaklaşım.


malloc/free vs new/delete

C dilinden gelen malloc/free fonksiyonları C++'ta da kullanılabilir. Ama new/delete tercih edilir.

#include <iostream>
#include <cstdlib>
using namespace std;

int main() {
    // C tarzi — malloc/free
    int* p1 = (int*)malloc(sizeof(int));
    *p1 = 42;
    cout << "malloc: " << *p1 << endl;
    free(p1);

    // C++ tarzi — new/delete
    int* p2 = new int(42);
    cout << "new: " << *p2 << endl;
    delete p2;

    return 0;
}

Fark Tablosu

Özellikmalloc/freenew/delete
DilC ve C++Sadece C++
Tip güvenliğiYok (void* döner, cast gerekir)Var (doğru tipi döner)
Constructor çağırır mı?HayırEvet
Destructor çağırır mı?HayırEvet
Hata durumuNULL dönerException fırlatır
Boyut hesaplamaManuel (sizeof)Otomatik
Override edilebilir mi?HayırEvet (operator new)

En kritik fark: malloc sadece ham bellek ayırır, new ise bellek ayırır ve constructor'ı çağırır. Nesnelerle çalışırken bu hayati önem taşır.

class Oyuncu {
    string isim;
public:
    Oyuncu(string i) : isim(i) {
        cout << isim << " olusturuldu" << endl;
    }
    ~Oyuncu() {
        cout << isim << " silindi" << endl;
    }
};

// new: constructor calisir, delete: destructor calisir
Oyuncu* o1 = new Oyuncu("Ali");    // "Ali olusturuldu"
delete o1;                          // "Ali silindi"

// malloc: constructor CALISMAZ!
Oyuncu* o2 = (Oyuncu*)malloc(sizeof(Oyuncu));
// o2->isim BASLATILMAMIS — tehlikeli!
free(o2);  // destructor da CALISMAZ

💡 İpucu: C++'ta her zaman new/delete kullan. malloc/free sadece C kütüphaneleriyle entegrasyonda gerekebilir. Ve ikisini asla karıştırma — new ile ayırdığını free ile silme, malloc ile ayırdığını delete ile silme.


Placement new (Kısa Bilgi)

İleri düzey bir konu ama bilmekte fayda var: placement new, önceden ayrılmış bir bellek alanında nesne oluşturmanı sağlar:

#include <iostream>
#include <new>
using namespace std;

int main() {
    // Onceden bir tampon alanımız var
    char tampon[sizeof(int)];

    // Bu alan uzerinde int olustur
    int* ptr = new(tampon) int(42);
    cout << *ptr << endl;  // 42

    // delete kullanma — bellek zaten stack'te
    // ptr->~int();  // Gerekirse destructor'ı manuel çağır

    return 0;
}

Placement new, oyun motorları ve yüksek performanslı sistemlerde bellek havuzu (memory pool) yönetiminde kullanılır. Şimdilik sadece varlığını bil.


Bellek Yönetimi Kontrol Listesi

Her new yazdığında kendine sor:

  1. ✅ Karşılığında delete var mı?

  2. new[] için delete[] mi kullanıyorum?

  3. ✅ Exception durumunda bellek temizleniyor mu?

  4. ✅ Pointer'ı kaybedecek bir atama yapıyor muyum?

  5. ✅ Delete sonrası pointer'ı nullptr yapıyor muyum?

  6. ✅ Acaba smart pointer kullanabilir miyim?

Son soru en önemlisi. Modern C++'ta raw new/delete kullanmak nadiren gerekir — smart pointer'lar neredeyse her durumu kapsar.


Özet

  • Stack hızlı ama sınırlı, heap büyük ama manuel yönetim gerektirir. Otomatik değişkenler stack'te, new ile oluşturulanlar heap'te yaşar.

  • `new` heap'te bellek ayırır ve pointer döndürür. `delete` o belleği geri verir. Dizi için new[] ve delete[] kullan.

  • Memory leak, ayrılan belleğin geri verilmemesidir — programı yavaşlatır ve sonunda çökertir. Her new'in bir delete'i olmalı.

  • `delete` sonrası pointer'ı `nullptr` yap — dangling pointer'ı önler.

  • `new`/`delete`, malloc/free'den üstündür — constructor/destructor çağırır, tip güvenlidir.

  • Modern C++'ta smart pointer kullan, raw new/delete'den mümkün olduğunca kaçın.