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 silinirHeap 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
| Özellik | Stack | Heap |
|---|---|---|
| Hız | Çok hızlı | Daha yavaş |
| Boyut | Sınırlı (1-8 MB) | Çok büyük (GB'larca) |
| Yönetim | Otomatik | Manuel (new/delete) |
| Yaşam süresi | Fonksiyon/blok sonuna kadar | delete'e kadar |
| Parçalanma | Yok | Olabilir (fragmentation) |
| Erişim | Sadece tanımlandığı blokta | Pointer 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: new ↔ delete, 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 oradaYaygı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?
Smart pointer kullan (en iyi çözüm — bir sonraki ders)
Her
newiçin mutlaka birdeleteolduğundan emin olErken return'lerden önce temizlik yap
RAII prensibini uygula (resource acquisition is initialization)
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
| Özellik | malloc/free | new/delete |
|---|---|---|
| Dil | C ve C++ | Sadece C++ |
| Tip güvenliği | Yok (void* döner, cast gerekir) | Var (doğru tipi döner) |
| Constructor çağırır mı? | Hayır | Evet |
| Destructor çağırır mı? | Hayır | Evet |
| Hata durumu | NULL döner | Exception fırlatır |
| Boyut hesaplama | Manuel (sizeof) | Otomatik |
| Override edilebilir mi? | Hayır | Evet (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:
✅ Karşılığında
deletevar mı?✅
new[]içindelete[]mi kullanıyorum?✅ Exception durumunda bellek temizleniyor mu?
✅ Pointer'ı kaybedecek bir atama yapıyor muyum?
✅ Delete sonrası pointer'ı nullptr yapıyor muyum?
✅ 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,
newile 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[]vedelete[]kullan.Memory leak, ayrılan belleğin geri verilmemesidir — programı yavaşlatır ve sonunda çökertir. Her
new'in birdelete'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.
AI Asistan
Sorularını yanıtlamaya hazır