Veri Yapıları ve Programlama

4 - Dinamik Bellek Yönetimi

Emre Can Yılmaz

Ondokuz Mayıs Üniversitesi

2026

Bellek Nedir?

  • Bellek, programlar çalışırken verilerin ve talimatların geçici olarak tutulduğu donanım kaynağıdır.
  • RAM, programın o anda kullandığı verileri hızlı erişimle saklar.
  • Farklı bellek türleri vardır; bu derste özellikle RAM ve programların RAM’i nasıl kullandığı ile ilgileneceğiz.

Bellek Yönetimi

Bellek yönetimi, programın çalışması sırasında ihtiyaç duyduğu alanın ayrılması ve işi bitince geri verilmesi ile ilgilidir.

  • Yüksek seviyeli dillerde bu süreç çoğunlukla otomatik yürütülür.
  • C dilinde ise bazı durumlarda belleği programcı yönetir.
  • Bu nedenle C’de bellek hataları (unutulan free, yanlış adres kullanımı vb.) daha sık görülür.

Hatırlatma: Pointer (Adres) Ne Demek?

  • Pointer, bellekte bir konumu (adresi) tutan değişkendir.
  • &xx değişkeninin adresi
  • *pp’nin gösterdiği adresteki değer (dereference)
int x = 5;
int *p = &x;   // p, x'in adresini tutar
printf("%d\n", *p);  // 5 (p'nin gösterdiği yerdeki değer)

Dinamik bellek fonksiyonları (malloc/calloc/realloc) bize bir adres döndürür.

Neden Dinamik Bellek Yönetimi?

Sabit boyutlu (önceden belirlenen) bellek kullanımının sınırları:

  • Programı yazarken ihtiyaç duyulacak bellek miktarı her zaman önceden bilinmeyebilir.
  • Örneğin diziler sabit boyutla tanımlanır; az tanımlarsanız yetmez, çok tanımlarsanız gereksiz alan kullanırsınız.
  • Program çalışırken (runtime) kullanıcı girdisine bağlı olarak bellek ihtiyacı artabilir/azalabilir.

Dinamik bellek yönetimi ne sağlar?

  • Program çalışırken ihtiyaç duyulduğunda bellek ayırmayı ve iş bitince geri vermeyi sağlar.
  • Değişken boyutlu yapılar (dinamik dizi, bağlı liste vb.) için temel yaklaşımdır.
  • Doğru kullanıldığında bellek kullanımını daha uygun seviyede tutmaya yardımcı olur.

Stack, Heap ve Global/Static

Bu derste üç temel alanı ayırt etmek yeterlidir:

  • Stack (otomatik bellek):

    • Fonksiyon içi yerel değişkenler
    • Fonksiyon bitince otomatik olarak “bırakılır”
  • Heap (dinamik bellek):

    • malloc/calloc/realloc ile ayrılan alan
    • Siz free etmeden geri verilmez
  • Global/Static alan:

    • Global değişkenler ve static ile tanımlananlar
    • Program boyunca yaşar (stack değildir)

Basit Bellek Şeması (Temsili)

Yüksek adresler
+------------------------------+
|            STACK             |  <- yerel değişkenler
|   (fonksiyon çağrıları)      |     (genelde aşağı doğru büyür)
+------------------------------+
|           (boşluk)           |
|                              |
+------------------------------+
|             HEAP             |  <- malloc/calloc/realloc
|   (dinamik ayrılan alan)     |     (genelde yukarı doğru büyür)
+------------------------------+
|      Global / Static alan    |  <- program boyunca yaşar
+------------------------------+
|            Kod alanı         |
+------------------------------+
Düşük adresler

Not: Bu çizim temsildir. Ayrıntılı bellek segmentlerini (text/BSS vb.) bu seviyede ayrı ayrı ele almayacağız.

Stack (Otomatik) ve Heap (Dinamik) Karşılaştırması

Özellik Stack (Otomatik) Heap (Dinamik)
Ayırma zamanı Fonksiyon çalışırken Program çalışırken istenen anda
Bırakma Fonksiyon bitince otomatik free() ile manuel
Boyut Genelde daha sınırlı Genelde daha esnek
Kullanım amacı Kısa ömürlü yerel değişkenler Değişken boyutlu veri yapıları

C’de Dinamik Bellek Yönetimi Fonksiyonları

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

  • malloc() : Bellek ayırma (ilk değer yok)
  • calloc() : Bellek ayırma + 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 bellek bloğu ayırır; içerik başlatılmamıştır.
  • Sözdizimi:
void* malloc(size_t size);
  • Geri dönüş:

    • Başarılıysa ayrılan bloğun başlangıç adresi
    • Başarısızsa NULL
    • Bu nedenle dönüş değerini kontrol etmek zorunludur.

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

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

int main(void) {
    int boyut = 10;

    // Önerilen kalıp: sizeof(*ptr)
    int *dizi = malloc((size_t)boyut * sizeof *dizi);

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

    for (int i = 0; i < boyut; i++) {
        dizi[i] = i * 2;
    }

    for (int i = 0; i < boyut; i++) {
        printf("%d ", dizi[i]);
    }
    printf("\n");
    free(dizi);
    dizi = NULL;
    return 0;
}

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

  • Görev: num * size kadar alan ayırır ve ayrılan baytları 0 yapar.
  • Sözdizimi:
void* calloc(size_t num, size_t size);
  • calloc ile ayrılan alan, pratikte tamsayılar için 0 başlangıcına denk gelir.

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

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

int main(void) {
    int boyut = 10;

    int *dizi = calloc((size_t)boyut, sizeof *dizi);

    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");

    for (int i = 0; i < boyut; i++) {
        printf("%d ", dizi[i]);   // hepsi 0 beklenir
    }
    printf("\n");
    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.
  • Sözdizimi:
void* realloc(void* ptr, size_t new_size);
  • Önemli: Dönecek adres, eski adresle aynı olmak zorunda değildir.
  • Başarısız olursa NULL döner ve eski blok yerinde kalır.

realloc() için Güvenli Kullanım Kuralı

  • realloc sonucunu doğrudan eski pointer’a yazmayın.
  • Önce geçici bir pointer ile kontrol edin.
int *tmp = realloc(dizi, yeni_boyut * sizeof *dizi);
if (tmp == NULL) {
    // dizi hâlâ geçerli, serbest bırakıp çıkabilirsiniz
    free(dizi);
    return 1;
}
dizi = tmp;

Örnek: Dinamik dizinin boyutunu büyütme

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

int main(void) {
    int eski_boyut = 5;
    int yeni_boyut = 10;

    int *dizi = malloc((size_t)eski_boyut * sizeof *dizi);
    if (!dizi) {
        printf("Bellek ayırma başarısız!\n");
        return 1;
    }

    for (int i = 0; i < eski_boyut; i++) {
        dizi[i] = i + 1;
    }

    printf("Başlangıç dizisi: ");
    for (int i = 0; i < eski_boyut; i++) printf("%d ", dizi[i]);
    printf("\n");

Devam…

    int *tmp = realloc(dizi, (size_t)yeni_boyut * sizeof *dizi);
    if (!tmp) {
        printf("Bellek yeniden boyutlandırma başarısız!\n");
        free(dizi);
        return 1;
    }
    dizi = tmp;

    // Yeni eklenen elemanları mutlaka BAŞLAT
    for (int i = eski_boyut; i < yeni_boyut; i++) {
        dizi[i] = 0;
    }

    printf("Büyütülmüş dizi: ");
    for (int i = 0; i < yeni_boyut; i++) printf("%d ", dizi[i]);
    printf("\n");

    free(dizi);
    dizi = NULL;

    return 0;
}

free() - Belleği Serbest Bırakma

  • Görev: Dinamik ayrılmış bir bloğu sisteme geri verir.
  • Sözdizimi:
void free(void* ptr);
  • ptr == NULL ise free(NULL) hiçbir şey yapmaz (güvenlidir).
  • free sonrası o adresi kullanmak tanımsız davranış üretir.

Örnek: Belleği serbest bırakma

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

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

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

    // ... dizi ile işlemler ...

    free(dizi);
    dizi = NULL;

    return 0;
}

Önemli Uyarılar

  • Belleği serbest bırakmayı unutmak (memory leak): Program uzun süre çalışırsa bellek gereksiz yere tüketilir.
  • Serbest bırakılmış belleğe erişmek (dangling pointer): Program hatalı sonuç üretebilir veya çökebilir.
  • Aynı bloğu iki kez serbest bırakmak (double free): Programın çökmesine veya beklenmeyen hatalara yol açabilir.

Yaygın Hatalar ve İyi Uygulamalar

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

void fonksiyon(void) {
    int *ptr = malloc(100 * sizeof *ptr);
    if (!ptr) return;

    // ... ptr ile bir şeyler yap ...

    // free(ptr);  // unutulursa: bellek sızıntısı
}

Sarkan İşaretçi (Dangling Pointer):

int *ptr = malloc(sizeof *ptr);
if (!ptr) return 1;

*ptr = 10;
free(ptr);

// ptr artık geçersiz bir adresi tutuyor
// printf("%d\n", *ptr);  // tanımsız davranış

ptr = NULL;   // iyi uygulama

Çift Serbest Bırakma (Double Free):

int *ptr = malloc(sizeof *ptr);
if (!ptr) return 1;

free(ptr);
// free(ptr);   // hatalı: double free

ptr = NULL;

İyi Uygulamalar

  • malloc/calloc/realloc dönüşünü mutlaka kontrol edin (NULL olabilir).
  • Boyut hesabında sizeof(*ptr) kalıbını tercih edin.
  • realloc için geçici pointer kullanın (tmp).
  • free sonrası pointer’ı NULL yapın.
  • Başlatılmamış (random) değeri okumayın; gerekiyorsa kendiniz başlatın.
  • Belleği bırakmak için free(ptr) kullanın; realloc(ptr, 0) gibi kalıpları bu derste kullanmayacağız.

Örnek Kullanım Alanları

Dinamik Diziler (Resizable Arrays):

  • Boyutu çalışma sırasında değişen diziler
  • malloc ile başlat, ihtiyaç olunca realloc ile büyüt/küçült

Dinamik Metinler (String):

  • Uzunluğu önceden bilinmeyen metinlerde kapasiteyi realloc ile artırma

Bağlı Liste gibi yapılar (ileride):

  • Her düğüm (node) genellikle malloc ile oluşturulur
struct Node {
    int data;
    struct Node *next;
};

Özet

  • Dinamik bellek, program çalışırken bellek ayırma ve geri verme imkânı sağlar.

  • C’de bu işlem malloc, calloc, realloc, free ile yapılır.

  • En sık hatalar: memory leak, dangling pointer, double free.

  • Güvenli kullanım için:

    • NULL kontrolü
    • sizeof(*ptr) kalıbı
    • realloc için geçici pointer
    • free sonrası NULL

Sorular?