Veri Yapıları ve Programlama

4 - Dinamik Bellek Yönetimi

Emre Can Yılmaz

Ondokuz Mayıs Üniversitesi

2025

Bellek Nedir?

  • Bellek, bilgisayarınızın programlar ve veriler için saklama alanı olarak kullandığı bir donanımdır.
  • RAM, programların çalışırken kullandıkları verileri ve talimatları depolar.
  • Farklı türde bellekler olsa da, biz bu rehberde RAM (Rastgele Erişimli Bellek) ile ilgileneceğiz.

Bellek Yönetimi

Bellek yönetimi, yazılımcıların en önemli görevlerinden biridir. Özellikle C gibi dillerde, yazılımcılar belleği manuel olarak yönetmek zorundadır. Bu, karmaşık ve hataya açık bir işlem olabilmektedir.

Neden Dinamik Bellek Yönetimi?

Statik (Sabit) Bellek Yönetimi Yetersizlikleri:

  • Statik Bellek: Programın derlenmesi sırasında belirli bir değişken için ayrılan bellektir.
  • Program yazılırken belleğin ne kadarının gerekeceği kesin olarak bilinmeyebilir.
  • Diziler gibi veri yapıları sabit boyutlu tanımlanır. Bu boyut aşıldığında sorunlar yaşanır, aşılmadığında ise bellek israfı olabilir.
  • Programın çalışma anında (runtime) bellek ihtiyacı değişebilir. Örneğin, kullanıcıdan alınan girdiye göre daha fazla veya daha az belleğe ihtiyaç duyulabilir.

Dinamik Bellek Yönetimi Çözümü:

  • Program çalışırken ihtiyaç duyulduğunda tahsis edilen bellektir.
  • Programın çalışma anında (runtime) bellek ayrılmasına (allocation) ve geri verilmesine (deallocation) olanak tanır.
  • Belleği ihtiyaç duyulduğu kadar kullanma imkanı sunar.
  • Daha esnek ve verimli programlar yazmamızı sağlar.

Yüksek seviyeli dillerde (Java, Python vb.) dinamik bellek yönetimi otomatik olarak yapılır. Ancak C dilinde yazılımcı bu yönetimi kendisi yapmalıdır.

Statik vs. Dinamik Bellek

Özellik Statik Bellek Yönetimi Dinamik Bellek Yönetimi
Bellek Ayırma Derleme zamanında (compile-time) Çalışma zamanında (runtime)
Bellek Miktarı Sabit, program başında belirlenir Değişken, program sırasında değişebilir
Esneklik Düşük Yüksek
Kontrol Otomatik (derleyici tarafından) Manuel (programcı tarafından)
Kullanım Alanı Yerel değişkenler, global değişkenler Değişken boyutlu veri yapıları, runtime boyut belirleme

C’de Dinamik Bellek Yönetimi Fonksiyonları

C dilinde dinamik bellek yönetimi için standart kütüphanede (stdlib.h) bulunan bazı fonksiyonlar kullanılır:

  • malloc(): Bellek ayırma
  • calloc(): Bellek ayırma ve sıfırlama
  • realloc(): Ayrılmış belleği yeniden boyutlandırma
  • free(): Ayrılmış belleği serbest bırakma

malloc() - Bellek Ayırma

  • Görev: Belirtilen boyutta ham (initialize edilmemiş) bellek bloğu ayırır.

  • Sözdizimi:

    void* malloc(size_t size);
    • size: Ayrılacak bellek bloğunun boyutu (byte cinsinden).
    • Geri Dönüş Değeri:
      • Başarılı olursa, ayrılan bellek bloğunun başlangıç adresini gösteren bir void* işaretçi döner.
      • Başarısız olursa (bellek yetersizliği vb.), NULL işaretçi döner. Dönüş değerini kontrol etmek çok önemlidir!

Örnek: 10 adet int için bellek ayırma

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *dizi;
    int boyut = 10;

    // 10 int için bellek ayır (10 * sizeof(int) byte)
    dizi = (int*)malloc(boyut * sizeof(int));

    // Bellek ayrılıp ayrılamadığını kontrol et
    if (dizi == NULL) {
        printf("Bellek ayırma başarısız!\n");
        return 1; // Hata kodu ile çıkış
    }

    printf("Bellek başarıyla ayrıldı.\n");
    // Diziyi kullan... (örneğin değer atama)
    for (int i = 0; i < boyut; i++) {
        dizi[i] = i * 2;
    }

    // Diziyi yazdır
    for (int i = 0; i < boyut; i++) {
        printf("%d ", dizi[i]);
    }
    printf("\n");

    // Belleği serbest bırakmayı UNUTMA!
    free(dizi);
    dizi = NULL; // Serbest bırakılan işaretçiyi NULL yapmak iyi bir uygulamadır.

    return 0; // Başarılı çıkış
}

calloc() - Bellek Ayırma ve Sıfırlama

  • Görev: Belirtilen sayıda ve boyutta bellek bloğu ayırır ve belleği sıfırlar (0 ile doldurur).

  • Sözdizimi:

    void* calloc(size_t num, size_t size);
    • num: Ayrılacak eleman sayısı.
    • size: Her bir elemanın boyutu (byte cinsinden).
    • Geri Dönüş Değeri: malloc() ile aynı şekilde, başarılıysa adres, başarısızsa NULL.
  • Temel Fark: calloc() ayrılan belleği sıfırlar, malloc() sıfırlamaz.

Örnek: 10 adet int için bellek ayırma ve sıfırlama

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *dizi;
    int boyut = 10;

    // 10 int için bellek ayır ve sıfırla
    dizi = (int*)calloc(boyut, sizeof(int));

    if (dizi == NULL) {
        printf("Bellek ayırma başarısız!\n");
        return 1;
    }

    printf("Bellek başarıyla ayrıldı ve sıfırlandı.\n");
    // Diziyi yazdır (sıfırlandığını kontrol et)
    for (int i = 0; i < boyut; i++) {
        printf("%d ", dizi[i]);
    }
    printf("\n");

    // Belleği serbest bırak
    free(dizi);
    dizi = NULL;

    return 0;
}

realloc() - Belleği Yeniden Boyutlandırma

  • Görev: Önceden ayrılmış bir bellek bloğunun boyutunu değiştirir (büyütür veya küçültür).

  • Sözdizimi:

    void* realloc(void* ptr, size_t new_size);
    • ptr: Yeniden boyutlandırılacak önceden ayrılmış bellek bloğunun işaretçisi.
    • new_size: Yeni istenen boyut (byte cinsinden).
    • Geri Dönüş Değeri:
      • Başarılı olursa, yeniden boyutlandırılmış bellek bloğunun başlangıç adresini gösteren bir void* işaretçi döner. Bu adres ptr ile aynı olmayabilir!
      • Başarısız olursa, NULL işaretçi döner. Orijinal bellek bloğu değişmeden kalır.
  • Önemli Noktalar:
    • realloc() yeni bir bellek bloğu ayırabilir ve içeriği eski bloktan yeni bloğa kopyalayabilir.
    • Eğer new_size 0 ise ve ptr NULL değilse, realloc() free(ptr) gibi davranır (belleği serbest bırakır).
    • ptr NULL ise, realloc(NULL, size) malloc(size) gibi davranır (yeni bellek ayırır).

Örnek: Dinamik dizinin boyutunu büyütme

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *dizi;
    int boyut = 5;

    // Başlangıçta 5 int için bellek ayır
    dizi = (int*)malloc(boyut * sizeof(int));
    if (dizi == NULL) {
        printf("Bellek ayırma başarısız!\n");
        return 1;
    }

    // Diziyi doldur
    for (int i = 0; i < boyut; i++) {
        dizi[i] = i + 1;
    }
    printf("Başlangıç dizisi: ");
    for (int i = 0; i < boyut; i++) {
        printf("%d ", dizi[i]);
    }
    printf("\n");

    // Dizinin boyutunu 10'a çıkar
    boyut = 10;
    int *yeni_dizi = (int*)realloc(dizi, boyut * sizeof(int)); // realloc'un dönüş değerini kontrol et!

    if (yeni_dizi == NULL) {
        printf("Bellek yeniden boyutlandırma başarısız!\n");
        free(dizi); // Orijinal belleği serbest bırak
        return 1;
    }

    dizi = yeni_dizi; // İşaretçiyi güncelle (önemli!)
    printf("Boyutu artırılmış dizi: ");
    for (int i = 0; i < boyut; i++) {
        printf("%d ", dizi[i]); // İlk 5 eleman korunmuş, sonraki 5 eleman tanımsız olabilir.
    }
    printf("\n");

    // Belleği serbest bırak
    free(dizi);
    dizi = NULL;

    return 0;
}

free() - Belleği Serbest Bırakma

  • Görev: Dinamik olarak ayrılmış bir bellek bloğunu sisteme geri verir (serbest bırakır). Bu bellek, başka dinamik bellek istekleri için tekrar kullanılabilir hale gelir.

  • Sözdizimi:

    void free(void* ptr);
    • ptr: Serbest bırakılacak bellek bloğunun başlangıç adresini gösteren işaretçi. Bu işaretçi daha önce malloc(), calloc() veya realloc() tarafından döndürülmüş olmalıdır.
    • ptr NULL ise, free(NULL) hiçbir şey yapmaz (güvenlidir).

Önemli

  • Belleği serbest bırakmayı UNUTMAK (memory leak - bellek sızıntısı) ciddi sorunlara yol açabilir. Program uzun süre çalıştığında sistem belleği tükenir ve program çökebilir veya sistem yavaşlayabilir.
  • Serbest bırakılmış belleğe erişmeye çalışmak (dangling pointer - sarkan işaretçi) tanımsız davranışa (undefined behavior) neden olur. Program hatalı çalışabilir veya çöökebilir.
  • Aynı bellek bloğunu birden fazla kez serbest bırakmak (double free - çift serbest bırakma) da hataya neden olur.

Örnek: Belleği serbest bırakma

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *dizi;
    dizi = (int*)malloc(10 * sizeof(int));

    if (dizi == NULL) {
        printf("Bellek ayırma başarısız!\n");
        return 1;
    }

    // ... Diziyi kullan ...

    // Belleği serbest bırak
    free(dizi);
    dizi = NULL; // İşaretçiyi NULL yapmak iyi bir uygulama

    return 0;
}

Yaygın Hatalar ve İyi Uygulamalar

Bellek Sızıntısı (Memory Leak):

  • Dinamik olarak ayrılan belleğin free() ile serbest bırakılmaması durumunda oluşur.
  • Program her bellek ayırdığında ve serbest bırakmadığında, kullanılabilir bellek miktarı azalır.
  • Uzun süre çalışan programlarda ciddi sorunlara yol açabilir.
// Hatalı örnek - Bellek sızıntısı
void fonksiyon() {
    int *ptr = (int*)malloc(100 * sizeof(int));
    // ... ptr ile bir şeyler yap ...
    // free(ptr); // UNUTULDU! Bellek sızıntısı
}

Sarkan İşaretçi (Dangling Pointer):

  • Serbest bırakılmış bir bellek bloğuna hala işaret eden bir işaretçidir.
  • Serbest bırakılmış belleğe erişmeye çalışmak istenmeyen davranışa neden olur.
int *ptr = (int*)malloc(sizeof(int));
*ptr = 10;
free(ptr);
// ptr artık sarkan bir işaretçi
// printf("%d\n", *ptr); // HATA! Tanımsız davranış
ptr = NULL; // Sarkan işaretçiyi önlemek için NULL yap

Çift Serbest Bırakma (Double Free):

  • Aynı bellek bloğunu birden fazla kez free() ile serbest bırakmaya çalışmak.
  • Genellikle programın çökmesine veya hatalı çalışmasına neden olur.
int *ptr = (int*)malloc(sizeof(int));
free(ptr);
// free(ptr); // HATA! Çift serbest bırakma
ptr = NULL;

İyi Uygulamalar:

  • Dinamik olarak ayrılan her belleği mutlaka free() ile serbest bırakın.
  • malloc(), calloc(), realloc() dönüş değerlerini kontrol edin. NULL dönmüşse bellek ayırma başarısız olmuştur.
  • Belleği serbest bıraktıktan sonra işaretçiyi NULL yapın (sarkan işaretçileri önlemek için).
  • Bellek yönetimini dikkatli yapın ve hatalardan kaçının. Bellek hataları bulması zor hatalardır.

Örnek Kullanım Alanları

Dinamik Diziler (Resizable Arrays):

  • Boyutu çalışma anında belirlenen veya değişen diziler.
  • malloc() ile başlangıç boyutu kadar bellek ayır, realloc() ile boyutu gerektiğinde büyüt.

Dinamik Karakter Dizileri (Strings):

  • Uzunluğu önceden bilinmeyen metinler.
  • malloc() ile yeterli bellek ayır, realloc() ile metin uzadıkça belleği büyüt.

Bağlı Listeler, Ağaçlar, Grafikler gibi Veri Yapıları:

  • Dinamik bellek yönetimi bu veri yapılarının temelini oluşturur.
  • Node’lar dinamik olarak oluşturulur ve birbirine bağlanır. (Bu konular ilerleyen haftalarda detaylı işlenecek.)

Özet

  • Dinamik bellek yönetimi, programların çalışma anında bellek ayırmasına ve serbest bırakmasına olanak tanır.
  • C dilinde malloc(), calloc(), realloc(), free() fonksiyonları ile yapılır.
  • Bellek sızıntısı, sarkan işaretçi, çift serbest bırakma gibi hatalardan kaçınmak önemlidir.
  • Dinamik bellek yönetimi, esnek ve verimli programlar yazmak için gereklidir.
  • Dinamik bellek yönetimi, daha karmaşık veri yapılarının (bağlı listeler, ağaçlar vb.) temelini oluşturur.

Sorular?