İçeriğe geç
academia.sh

Ders 11 / 15

Bellek Düzeni

Bir programın belleği tek bir alan değildir; otomatik, statik ve dinamik olmak üzere ayrı bölgelere bölünür ve bir bloğun hangi bölgede durduğu, onun sahibinin kim olacağını baştan belirler. Ders dört dil gözlemini ayir ve birak gözlemiyle yan yana koyup bu ayrımı adres basmadan ölçüyor.

Yayın Güncelleme

Bu derste

  • Otomatik, statik ve dinamik bölgelerin sahiplik sınıflarıyla nasıl örtüştüğünü gösterir
  • Dört dil gözlemini ayir ve birak gözlemiyle karşılaştırarak sayaç farkının yalnız dinamik bölgede kıpırdadığını ölçer
  • Bölge sınırlarını ve hizalama değerlerini basmadan, bölgenin sahiplikle ilişkisini açıklar
İçindekiler

Dinamik Bellek konusunun önceki dört dersi belleğin dinamik bölgesinde kaldı: malloc, calloc, realloc, sahiplik sözleşmesi, sızıntı, denetim noktası — hepsi çalışma zamanında ayrılan ve elle bırakılan bloklarla ilgiliydi. Ama bu kursun ilk dersinden beri her ölçümde dil sınıfı da vardı: yerel değişkenler, statik alanlar, dizgi değişmezleri. Bu ders, konuyu kapatırken, dinamik bölgeyi geri kalan üç bölgenin yanına koyuyor ve bir bloğun hangi bölgede durduğunun, sahibinin kim olacağını nasıl baştan belirlediğini gösteriyor.

Dört Bölge, Bir Soru

Bir C programının belleği birden çok bölgeye ayrılır: kodun kendisi, sabit veri, statik değişkenler, çalışma zamanı yığıtı (otomatik değişkenler için) ve öbek (dinamik ayırma için). Bu bölgelerin donanımda nasıl yerleştiği ve aralarındaki sınırların nasıl hizalandığı Bilgisayarlar Nasıl Çalışır kursunda (M01/K01) ele alındı; bu ders o düzeni yeniden anlatmıyor, yalnız sahiplik sorusuyla ilişkilendiriyor. Soru hep aynı: bu bloğu kim bırakacak? Yanıt, bloğun hangi bölgede durduğuna bağlı.

// sayac.h — her ayirmayi ve her birakmayi sayan sarmalayici
#ifndef SAYAC_H
#define SAYAC_H

#include <stddef.h>

void *say_ayir(size_t boyut);
void *say_buyut(void *eski, size_t boyut);
void say_birak(void *blok);
int ayrilan_sayisi(void);
int birakilan_sayisi(void);

#endif
// sayac.c — sayaclar yalniz bu birimde tutulur
#include <stdlib.h>
#include "sayac.h"

static int ayrilan = 0;
static int birakilan = 0;

void *say_ayir(size_t boyut) {
    void *blok = malloc(boyut);
    if (blok != NULL) {
        ayrilan++;
    }
    return blok;
}

void *say_buyut(void *eski, size_t boyut) {
    int yeni_blok = (eski == NULL);
    void *yeni = realloc(eski, boyut);
    if (yeni != NULL && yeni_blok) {
        ayrilan++;
    }
    return yeni;
}

void say_birak(void *blok) {
    if (blok != NULL) {
        birakilan++;
    }
    free(blok);
}

int ayrilan_sayisi(void) {
    return ayrilan;
}

int birakilan_sayisi(void) {
    return birakilan;
}
// duzen.c — dort dil gozlemi ile ayir ve birak karsitligi
#include <stdio.h>
#include <string.h>
#include "sayac.h"

static int sabit_statik = 3;

static int fark(void) {
    return ayrilan_sayisi() - birakilan_sayisi();
}

static void gozle(const char *ad, const char *sinif, const char *kanit) {
    printf("%-30s%-10s%s\n", ad, sinif, kanit);
}

int main(void) {
    char tampon[64];
    int once;

    printf("%-30s%-10s%s\n", "gozlem", "sahip", "kanit");

    /* --- dil: otomatik bolge, kapsam bitince kendiliginden geri alinir --- */
    once = fark();
    int yerel = 5;
    int *p = &yerel;
    snprintf(tampon, sizeof tampon, "*p %d, sayac farki %d", *p, fark() - once);
    gozle("yerel degisken", "dil", tampon);

    /* --- dil: statik bolge, program boyunca tek bir yerde durur --- */
    int *s = &sabit_statik;
    snprintf(tampon, sizeof tampon, "*s %d, birakma cagrisi yok", *s);
    gozle("statik alan", "dil", tampon);

    /* --- dil: sabit veri bolgesi, degismez metin --- */
    const char *degismez = "sabit";
    snprintf(tampon, sizeof tampon, "uzunluk %d, birakma cagrisi yok", (int)strlen(degismez));
    gozle("dizgi degismezi", "dil", tampon);

    /* --- dil: otomatik bolgede yasayan bir dizi --- */
    int dizi[4] = {1, 2, 3, 4};
    int *ilk = dizi;
    int *son = &dizi[3];
    snprintf(tampon, sizeof tampon, "son - ilk %d, *son %d", (int)(son - ilk), *son);
    gozle("yerel dizi", "dil", tampon);

    /* --- cagiran: obek bolgesi, elle ayrilir ve elle birakilir --- */
    once = fark();
    int *blok = say_ayir(4 * sizeof *blok);
    int toplam = 0;
    if (blok != NULL) {
        for (int i = 0; i < 4; i++) {
            blok[i] = i + 1;
            toplam += blok[i];
        }
    }
    say_birak(blok);
    snprintf(tampon, sizeof tampon, "toplam %d, sayac farki %d", toplam, fark() - once);
    gozle("ayir ve birak", "cagiran", tampon);

    return 0;
}
gozlem                        sahip     kanit
yerel degisken                dil       *p 5, sayac farki 0
statik alan                   dil       *s 3, birakma cagrisi yok
dizgi degismezi               dil       uzunluk 5, birakma cagrisi yok
yerel dizi                    dil       son - ilk 3, *son 4
ayir ve birak                 cagiran   toplam 10, sayac farki 0

BE16. Dört dil gözleminin dördünde de sayaç hiç kıpırdamadı — yerel degisken ve yerel dizi otomatik bölgede, statik alan statik bölgede, dizgi degismezi sabit veri bölgesinde durur; üçü de programın kendisi tarafından, kapsam ya da program ömrüyle geri alınır. ayir ve birak gözlemi ise öbek bölgesindedir ve sayaç farkı yine 0 verir — ama bu kez sıfır, dilin kendiliğinden yaptığı bir şeyden değil, say_ayir’in karşılığında yazılan say_birak’tan geliyor. Aynı sonuç (fark 0), iki farklı nedenden geliyor: biri hiç ölçülmediği için sıfır, öteki ölçülüp dengelendiği için sıfır.

Bölge Sahibi Belirler, Sahip Bölgeyi Belirlemez

Bir bloğun hangi bölgede durduğu, çoğu zaman bildirimin kendisinden bellidir: bir işlev içinde int yerel = 5; yazmak otomatik bölgeye, dosya kapsamında ya da static anahtar sözcüğüyle bildirmek statik bölgeye, bir dizgi değişmezi yazmak sabit veri bölgesine, ve say_ayir (ya da doğrudan malloc) çağırmak öbek bölgesine yerleştirir. Sahiplik sınıfı bu bildirimden türer: otomatik ve statik bölgedeki hiçbir blok bırakma çağrısı almaz, sahibi hep dildir; öbek bölgesindeki her blok bir bırakma çağrısı bekler, sahibi çağırandır (ya da Serbest Bırakma Disiplini dersinde görüldüğü gibi, sözleşmeye göre çağrılandır). Bölge tek başına yeterli değildir — öbek bölgesindeki bir bloğun çağırana mı çağrılana mı ait olduğunu bölgenin kendisi söylemez, bunu yine sahip.h gibi bir sözleşme belirler. Ama otomatik ve statik bölge için bölge tek başına yeterlidir: oraya yerleşen hiçbir blok hiçbir zaman elle bırakılmaz.

İç İçe Kapsam da Dil Sınıfındadır

Yukarıdaki dört dil gözlemi hepsi main’in en dış kapsamındaydı. Ama otomatik bölgenin kuralı kapsamın derinliğine bakmaz — bir işlevin gövdesi içindeki her { } bloğu kendi otomatik ömrünü taşır ve iç içe geçmiş bir blok da dış blok kadar dil sınıfındadır. Aynı zamanda, dinamik tarafta bir bloğun boyutu büyüdükçe bunun sahiplik sınıfını değiştirmediğini de göstermek mümkün: ister dört elemanlık ister kırk elemanlık olsun, say_ayir’den dönen her blok yine çağırana aittir.

// icice.c — ic ice otomatik kapsam ile boyutu buyuyen obek bloginin karsitligi
#include <stdio.h>
#include "sayac.h"

static int fark(void) {
    return ayrilan_sayisi() - birakilan_sayisi();
}

static void gozle(const char *ad, const char *sinif, const char *kanit) {
    printf("%-30s%-10s%s\n", ad, sinif, kanit);
}

int main(void) {
    char tampon[64];
    int once;

    /* --- dil: ic ice bir blok icindeki otomatik degisken --- */
    int disarida = 1;
    {
        int icerde = 2;
        {
            int en_icerde = 3;
            snprintf(tampon, sizeof tampon,
                     "disarida %d, icerde %d, en_icerde %d, sayac farki %d",
                     disarida, icerde, en_icerde, fark());
            gozle("uc kat ic ice kapsam", "dil", tampon);
        }
    }

    /* --- cagiran: boyutu on kat buyuyen bir obek blogu yine cagirana ait --- */
    once = fark();
    enum { KUCUK = 4, BUYUK = 40 };
    int *kucuk_blok = say_ayir(KUCUK * sizeof *kucuk_blok);
    int *buyuk_blok = say_ayir(BUYUK * sizeof *buyuk_blok);
    int toplam = 0;
    if (kucuk_blok != NULL && buyuk_blok != NULL) {
        for (int i = 0; i < KUCUK; i++) {
            toplam += kucuk_blok[i] = i;
        }
        for (int i = 0; i < BUYUK; i++) {
            toplam += buyuk_blok[i] = i;
        }
    }
    say_birak(kucuk_blok);
    say_birak(buyuk_blok);
    snprintf(tampon, sizeof tampon, "toplam %d, sayac farki %d", toplam, fark() - once);
    gozle("kucuk ve buyuk blok", "cagiran", tampon);

    return 0;
}
uc kat ic ice kapsam          dil       disarida 1, icerde 2, en_icerde 3, sayac farki 0
kucuk ve buyuk blok           cagiran   toplam 786, sayac farki 0

BE17. uc kat ic ice kapsam gözleminde üç değişken üç ayrı { } derinliğinde bildirildi, ama üçü de otomatik bölgede ve üçü de sayacı hiç etkilemedi — kapsamın derinliği, dil sınıfının geçerliliğini değiştirmiyor. kucuk ve buyuk blok gözlemi ise boyutu on kat farklı iki öbek bloğunu (4 ve 40 eleman) yan yana ayırıp bırakıyor; sayaç farkı yine 0, ama bu kez iki ayrı say_ayir/say_birak çiftinin dengelenmesinden. Bu, Bölge Sahibi Belirler bölümündeki iddiayı derinliğe ve boyuta karşı sınıyor: bir bloğun otomatik mi öbek mi olduğunu belirleyen ne kapsamın kaç kat iç içe olduğu ne de bloğun kaç bayt tuttuğu — belirleyen yalnız hangi bildirimle var olduğudur. int en_icerde üç kapsam derinliğinde de olsa dil sınıfındandır; say_ayir(40 * sizeof(int)) tek bir çağrıda da olsa çağıran sınıfındandır.

Aynı Sınıf, Farklı Kalıcılık

dil sınıfındaki hiçbir blok elle bırakılmaz — ama bu, dil sınıfının içindeki her bölgenin aynı davrandığı anlamına gelmez. Otomatik bölge her çağrıda sıfırdan başlar, statik bölge çağrılar arasında kalıcıdır. İkisi de sahiplik sorusuna aynı yanıtı verir (sahip dil, bırakma çağrısı yok), ama ikisinin ömrü birbirinden tamamen farklıdır.

// kalici.c — islev icindeki static degiskenin cagrilar arasinda kalici olmasi
#include <stdio.h>
#include "sayac.h"

static int fark(void) {
    return ayrilan_sayisi() - birakilan_sayisi();
}

static int sayac_tut(void) {
    static int cagri = 0;
    cagri++;
    return cagri;
}

static int yerel_sifirlanir(void) {
    int deger = 0;
    deger++;
    return deger;
}

int main(void) {
    char tampon[96];
    int once = fark();

    int birinci = sayac_tut();
    int ikinci = sayac_tut();
    int ucuncu = sayac_tut();

    int otomatik_birinci = yerel_sifirlanir();
    int otomatik_ikinci = yerel_sifirlanir();

    snprintf(tampon, sizeof tampon,
             "static cagrilar %d %d %d, otomatik cagrilar %d %d, sayac farki %d",
             birinci, ikinci, ucuncu, otomatik_birinci, otomatik_ikinci, fark() - once);
    printf("%-30s%s\n", "kalici ile sifirlanan", tampon);

    return 0;
}
kalici ile sifirlanan         static cagrilar 1 2 3, otomatik cagrilar 1 1, sayac farki 0

BE18. sayac_tut içindeki cagri değişkeni static bildirildiği için statik bölgede durur ve üç çağrı boyunca aynı değeri taşır: 1, 2, 3. yerel_sifirlanir içindeki deger ise otomatik bölgede, her çağrıda yeniden bildirilir ve 0’dan başlar; iki çağrının ikisi de 1 döndürüyor, çünkü ikinci çağrı birincinin bıraktığı yerden devam etmiyor. Sayaç farkı hâlâ 0, çünkü hiçbir öbek ayırması yapılmadı — bu ölçüm sahiplik sınıfını değil, aynı sınıfın içindeki ömür farkını gösteriyor. Bölge Sahibi Belirler, Sahip Bölgeyi Belirlemez bölümündeki kural burada da geçerli: static anahtar sözcüğü bir değişkeni otomatik bölgeden statik bölgeye taşır ve bu taşıma, hâlâ hiçbir bırakma çağrısı gerektirmeyen dil sınıfının içinde kalır — ama kalıcılığı kökten değiştirir.

Sınırlayıcı Ölçüm

Bölgeler arasındaki sınırların nerede durduğu ve bir bloğun hangi hizalama değerine göre yerleştiği çalışma zamanı ortamına bağlıdır; bu ders ne bir adres ne bir hizalama değeri basıyor. son - ilk farkı 3 verdi — bu, iki adresin kendisi değil, aralarındaki eleman sayısıdır ve bölge sınırından bağımsız, taşınabilir bir ölçüdür.

Otomatik bölgenin boyutu da sınırsız değildir — her çağrı çerçevesi, o bölgede sınırlı bir yer kaplar ve iç içe kapsamlar (uc kat ic ice kapsam gözlemindeki gibi) ya da derin bir özyineleme bu yeri tüketebilir. Bu sınırın aşılması tanımsız davranıştır (yaygın adıyla yığıt taşması) ve bu ders, kursun geri kalanındaki tanımsız davranış kuralına uyarak bunu hiç koşturmuyor — yalnız var olduğunu belirtiyor. Öbek bölgesinin sınırı ise farklı bir biçimde görünür: say_ayir boyutu karşılayamadığında sessizce başarısız olmaz, malloc’ın sözleşmesine uygun biçimde NULL döndürür ve bu ders boyunca her ayırma bu dönüşle kontrol edildi (kucuk_blok != NULL && buyuk_blok != NULL). Otomatik bölgenin taşması bir çökmeyle, öbek bölgesinin dolması bir NULL dönüşüyle kendini gösterir — ikisi de bu bölgelerin sınırsız olmadığının kanıtı, ama biri denetlenebilir bir dönüş değeri üretir, öteki üretmez.

Özet

  • Bellek dört bölgeye ayrılır: otomatik, statik, sabit veri ve öbek; bu dersin ölçtüğü bellek sınıfı dildir.
  • Otomatik ve statik bölgedeki bloklar hiçbir zaman elle bırakılmaz — sahibi dil, sayaç hiç kıpırdamaz.
  • Öbek bölgesindeki bloklar say_ayir/say_birak ile dengelenir; sayaç farkı yine 0 çıkabilir ama bu kez ölçülüp dengelendiği için.
  • Bölge, otomatik ve statik bellek için sahiplik sınıfını tek başına belirler; öbek bölgesinde sınıfı belirleyen bölge değil, sözleşmedir.
  • Bölge sınırları ve hizalama değerleri ortama bağlıdır; bu derste basılmadı, M01/K01’e bırakıldı.

Sonraki Adım

Dinamik Bellek konusu burada kapanıyor: dört sahiplik sınıfının (dil, çağıran, çağrılan, sahipsiz) her biri en az bir ölçümle görüldü. Sıradaki konu, Sağlamlık, sözleşmenin kırıldığı anı konu alıyor — bir kural yazılı olduğu hâlde bilerek çiğnendiğinde ne olduğunu, hangi durumların yalnız düzyazıda tarif edilip hiç koşturulmadığını ve yanlarına hangi tanımlı karşılığın koşturulduğunu.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat