İçeriğe geç
Educora
İleri20 dk7 / 8

Dinamik bellek ve yapılar

malloc, calloc ve free ile program çalışırken bellek ayır ve geri ver, NULL kontrolü yap, bellek sızıntısından kaçın ve ilişkili verileri struct ve typedef ile bir araya getir.

Kendini test et
Bu derste öğreneceklerin
  • Yığıt (stack) ile dinamik bellek (heap) arasındaki farkı açıklamak
  • malloc ve calloc ile bellek ayırmak, sonucu NULL için denetlemek ve free ile geri vermek
  • Bellek sızıntılarını ve serbest bırakılmış belleği kullanma hatalarını tanımak
  • struct ve typedef ile kendi türünü oluşturmak ve alanlara . ve -> ile erişmek

Bir okul için elektronik not defteri yazıyorsun. Bir sınıfta kaç öğrenci olacak: 12, 25 mi, yoksa 40 mı? Programı yazarken bunu bilemezsin ama bir dizinin boyutu önceden belli olmalıdır. Üstelik her öğrenci için ad, yaş ve not ortalaması gibi birkaç bilgi saklaman gerekir. Bu derste iki sorunu da çözeceğiz: program çalışırken tam gerektiği kadar belleği dinamik olarak ayıracak ve ilişkili verileri yapılarda (struct) toplayacağız.

Yığıt ve yığın

Bir fonksiyonun yerel değişkenleri yığıtta (stack) yaşar: fonksiyon başlarken oluşur, bitince kendiliğinden silinir. Yığıt hızlıdır ama küçüktür ve boyutların derleme sırasında bilinmesi gerekir. Dinamik bellek (yığın, İngilizcesiyle heap) ise büyük bir depodur: ondan istediğin an istediğin boyutta yer isteyebilirsin. Ama bir şartla: işin bitince o yeri kendin geri vermelisin. Gereken tüm fonksiyonlar stdlib.h başlığındadır.

FonksiyonNe yapar
malloc(size)size baytlık bir blok ayırır; içeriği rastgeledir
calloc(n, size)her biri size bayt olan n eleman için yer ayırır ve hepsini sıfırla doldurur
realloc(p, size)önceden ayrılmış bir bloğu büyütür veya küçültür
free(p)bloğu sisteme geri verir
Bellek yetmezse malloc, calloc ve realloc NULL döndürür.
C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n = 5;
    int *squares = malloc(n * sizeof(int));
    if (squares == NULL) {
        printf("Not enough memory\n");
        return 1;
    }
    for (int i = 0; i < n; i++) {
        squares[i] = (i + 1) * (i + 1);
    }
    for (int i = 0; i < n; i++) {
        printf("%d%s", squares[i], i < n - 1 ? ", " : "\n");
    }
    free(squares);
    squares = NULL;
    return 0;
}
Beklenen çıktı
1, 4, 9, 16, 25
Burada n sabittir ama kullanıcının girdiği bir sayı da olabilirdi. Ayrılan blokla sıradan bir dizi gibi çalışıyoruz.

calloc belleği sıfırlarla doldurduğu için sayaçlarda kullanışlıdır. Aşağıdaki program bir oylamada her seçeneğin kaç oy aldığını sayar:

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

int main(void) {
    int votes[] = {2, 0, 1, 2, 2, 1};
    int n = 6, options = 3;
    int *counts = calloc(options, sizeof(int));
    if (counts == NULL) {
        return 1;
    }
    for (int i = 0; i < n; i++) {
        counts[votes[i]]++;
    }
    for (int i = 0; i < options; i++) {
        printf("Option %d: %d\n", i, counts[i]);
    }
    free(counts);
    return 0;
}
Beklenen çıktı
Option 0: 1
Option 1: 2
Option 2: 3
C
for (int i = 0; i < 1000; i++) {
    int *tmp = malloc(1000 * sizeof *tmp);
    if (tmp == NULL) {
        break;
    }
    tmp[0] = i;
}
HATA örneği: döngünün her adımında yaklaşık 4000 bayt ayrılıyor ama free(tmp) yok; toplamda yaklaşık 4 milyon bayt sızıyor.

Yapılar (struct)

Tanım
Yapı (struct)

Farklı türlerdeki birkaç alanı tek bir ad altında toplayan, kullanıcının tanımladığı tür. Örneğin bir öğrenci yapısında ad (char dizisi), yaş (int) ve not ortalaması (double) olabilir.

C
#include <stdio.h>
#include <string.h>

struct Student {
    char name[20];
    int age;
    double average;
};

int main(void) {
    struct Student s1;
    strcpy(s1.name, "Aysel");
    s1.age = 15;
    s1.average = 4.75;
    struct Student s2 = {"Murad", 16, 4.2};
    printf("%s, %d, %.2f\n", s1.name, s1.age, s1.average);
    printf("%s, %d, %.2f\n", s2.name, s2.age, s2.average);
    return 0;
}
Beklenen çıktı
Aysel, 15, 4.75
Murad, 16, 4.20
Alanlara nokta ile erişilir: s1.age. Bir yapıya, alanlarının sırasıyla süslü parantez içinde ilk değer verilebilir.

Her seferinde struct Student yazmamak için typedef ile türe kısa bir ad verilir. Bir yapıya işaretçi olduğunda alana -> operatörüyle erişilir: p->age, (*p).age demektir. Bir yapıyı fonksiyona işaretçiyle aktarmak hem onu değiştirmeye olanak tanır hem de tüm yapıyı kopyalamaktan daha ucuzdur.

C
#include <stdio.h>

typedef struct {
    char name[20];
    int age;
    double average;
} Student;

void birthday(Student *s) {
    s->age++;
}

void print_student(const Student *s) {
    printf("%s, %d, %.2f\n", s->name, s->age, s->average);
}

int main(void) {
    Student group[3] = {
        {"Aysel", 15, 4.75},
        {"Murad", 16, 4.2},
        {"Leyla", 15, 4.5}
    };
    birthday(&group[1]);
    for (int i = 0; i < 3; i++) {
        print_student(&group[i]);
    }
    return 0;
}
Beklenen çıktı
Aysel, 15, 4.75
Murad, 17, 4.20
Leyla, 15, 4.50
Yapılardan oluşan bir dizi ve işaretçiyle çalışan fonksiyonlar: birthday, Murad'ın yaşını 16'dan 17'ye çıkardı.

Önemli noktalar

  • Yerel değişkenler yığıtta kendiliğinden oluşur ve silinir; dinamik bellek ise free'ye kadar yaşar.
  • malloc rastgele içerikli, calloc ise sıfırlanmış bellek ayırır; ikisinin sonucunu da NULL için denetle.
  • Her malloc için bir free; yoksa bellek sızar.
  • struct farklı türlerdeki alanları bir araya getirir, typedef bir türe kısa ad verir.
  • Değişkende alana . ile, işaretçide -> ile erişilir: p->age = (*p).age.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
Yeterli bellek yoksa malloc ne döndürür?