---
title: "C'de Bellek ve İşaretçiler"
source: 'https://academia.sh/tr/kurslar/c-bellek'
language: tr
updated: '2026-09-26T03:02:42+00:00'
license: 'CC BY-SA 4.0'
lessons: 15
outcomes:
    - 'İşaretçi aritmetiğini ve dizi ile işaretçi ilişkisini adres basmadan ölçer'
    - 'Bir bellek bloğunun sahibini dil, çağıran, çağrılan ya da hiç kimse olarak ayırt eder'
    - 'Ayırma ve bırakma işlevlerini sahiplik sözleşmesiyle birlikte kullanır'
    - 'Bellek sızıntısını sayaç farkıyla ölçer ve önler'
    - 'Tanımsız davranışın kaynaklarını tanır ve yerine tanımlı karşılığını yazar'
---

# C'de Bellek ve İşaretçiler

C'de bir bellek bloğunu kimin bırakacağı kaynakta yazılı değildir; bilgi bir sözleşmeyle taşınır ve tip sistemi onu denetlemez: kurs her düzeneği tek bir soruyla ölçer — bu belleği dil mi, çağıran mı, çağrılan mı sahipleniyor, yoksa kimse mi? İşaretçiler, işaretçi aritmetiği, dinamik bellek, sızıntılar, tanımsız davranış ve sınır denetimi aynı çerçeveden geçirilir.

## İşaretçiler

Adres alma ve işaret edilen değere erişme, işaretçi aritmetiğinin eleman cinsinden ilerlemesi, dizinin işaretçiye bozunması, boş ve sarkan işaretçiler, opak işaretçilerle tip gizleme ve fonksiyon işaretçileriyle geri çağrı — her biri işaret edilen belleğin sahibi sorusuyla birlikte.

### İşaretçi Temelleri

Source: https://academia.sh/tr/kurslar/c-bellek/isaretci-temelleri

Bir bellek bloğunu kimin sahiplendiği sorusu; beş dosyalık ölçüm çekirdeğinin tam koşumu ve dört sınıfın (dil, çağıran, çağrılan, sahipsiz) 4/4/3/2 dağılımı, ardından adres alma ve dolaylama işleçlerinin adres basılmadan ölçülmesi.

C Temelleri kursu bir kararın hangi aşamada (ön işlemci, derleyici, bağlayıcı, koşum)
verildiğini ölçtü. Bu kurs farklı bir soruyla başlıyor: kaynakta hiçbir aşama bir bellek
bloğunu kimin **bırakacağını** söylemiyor. Derleyici bir bildirimin tipini bilir, bağlayıcı
bir adın hangi tanıma bağlandığını çözer, koşum yalnız sonucu görür — ama "bu bloğu kim
serbest bırakacak" sorusunun yanıtı hiçbirinde durmuyor. Bu yanıt, kaynakta yazılı bir
**sözleşmeyle** taşınıyor: bir başlık dosyasındaki yorum, bir işlev adı, bir alışkanlık.

Kurs bunu tek bir soruyla ölçüyor: **bu belleği kim sahipleniyor?** Yanıt dört sınıftan
birine düşüyor ve bu dört ad kurs boyunca değişmiyor.

#### Dört Sınıf: Dil, Çağıran, Çağrılan, Sahipsiz

| Sınıf | Tanım |
|---|---|
| **dil** | Ömrü dil belirler (otomatik ya da statik); kimse bırakma çağrısı yapmaz |
| **çağıran** | Belleği ayıran ya da bir işlevden alan taraf bırakmakla yükümlüdür |
| **çağrılan** | Belleği bir modül tutar; çağırana yalnız ödünç verir, hata yolunda da kendisi temizler |
| **sahipsiz** | Hiçbir taraf bırakmakla yükümlü değildir; blok yalnız bir sayaç farkı olarak görünür |

Bu dört sınıfı beş dosyalık bir ölçüm çekirdeği kuruyor: `sayac.h`/`sayac.c` her ayırma ve
bırakma çağrısını sayan ince bir sarmalayıcı, `sahip.h`/`sahip.c` opak bir kaydın sahiplik
sözleşmesini yorumlarda taşıyan bir birim, `sahiplik.c` ise on üç gözlemi tek bir koşumda
toplayan ölçüm işlevi. Kursun geri kalan on dört dersi bu çekirdeğin **kesitlerini** alacak;
bu ders tek istisna olarak tamamını çalıştırıyor.

```c
// 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
```

```c
// 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;
}
```

```c
// sahip.h — kaydin ici gizli, sahiplik sozlesmesi yorumlarda yazili
#ifndef SAHIP_H
#define SAHIP_H

#include <stddef.h>

typedef struct Kayit Kayit;

/* Cagiran sahiplenir ve kayit_yok_et ile birakir. Ad sinira sigmazsa NULL
   doner; o ana kadar ayrilan bellegi islev kendisi birakir. */
Kayit *kayit_olustur(const char *ad, size_t sinir);

/* Kaydi ve kaydin icindeki ad tamponunu birlikte birakir. */
void kayit_yok_et(Kayit *k);

/* Kayit sahiplenir: donen isaretci kayit yok edilene dek gecerlidir,
   cagiran onu birakmaz. */
const char *kayit_ad(const Kayit *k);

/* Cagiran sahiplenir: donen kopya say_birak ile birakilir. */
char *kayit_ad_kopyasi(const Kayit *k);

#endif
```

```c
// sahip.c — sozlesmenin uygulandigi birim
#include <string.h>
#include "sahip.h"
#include "sayac.h"

struct Kayit {
    char *ad;
    size_t uzunluk;
};

Kayit *kayit_olustur(const char *ad, size_t sinir) {
    Kayit *k = say_ayir(sizeof *k);
    if (k == NULL) {
        return NULL;
    }
    k->uzunluk = strlen(ad);
    if (k->uzunluk >= sinir) {
        say_birak(k);
        return NULL;
    }
    k->ad = say_ayir(k->uzunluk + 1);
    if (k->ad == NULL) {
        say_birak(k);
        return NULL;
    }
    memcpy(k->ad, ad, k->uzunluk + 1);
    return k;
}

void kayit_yok_et(Kayit *k) {
    if (k == NULL) {
        return;
    }
    say_birak(k->ad);
    say_birak(k);
}

const char *kayit_ad(const Kayit *k) {
    return k->ad;
}

char *kayit_ad_kopyasi(const Kayit *k) {
    char *kopya = say_ayir(k->uzunluk + 1);
    if (kopya != NULL) {
        memcpy(kopya, k->ad, k->uzunluk + 1);
    }
    return kopya;
}
```

```c
// sahiplik.c — bir bellegi kimin sahiplendigini olcen cekirdek
#include <stdio.h>
#include <string.h>
#include "sahip.h"
#include "sayac.h"

static int statik_sayac = 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 dil = 0, cagiran = 0, cagrilan = 0, sahipsiz = 0;
    int once, ayrilan_once, birakilan_once;

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

    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++;

    int *s = &statik_sayac;
    snprintf(tampon, sizeof tampon, "*s %d, birakma cagrisi yok", *s);
    gozle("statik alan", "dil", tampon);
    dil++;

    const char *degismez = "sabit";
    snprintf(tampon, sizeof tampon, "uzunluk %d, birakma cagrisi yok", (int)strlen(degismez));
    gozle("dizgi degismezi", "dil", tampon);
    dil++;

    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);
    dil++;

    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);
    cagiran++;

    once = fark();
    int *buyuyen = say_buyut(NULL, 2 * sizeof *buyuyen);
    int korunan = 0;
    if (buyuyen != NULL) {
        buyuyen[0] = 7;
        int *yeni = say_buyut(buyuyen, 8 * sizeof *yeni);
        if (yeni != NULL) {
            buyuyen = yeni;
        }
        korunan = buyuyen[0];
    }
    say_birak(buyuyen);
    snprintf(tampon, sizeof tampon, "ilk eleman %d, sayac farki %d", korunan, fark() - once);
    gozle("yeniden boyutlandirma", "cagiran", tampon);
    cagiran++;

    ayrilan_once = ayrilan_sayisi();
    Kayit *k = kayit_olustur("defter", 16);
    snprintf(tampon, sizeof tampon, "bos degil %d, ayrilan blok %d", k != NULL, ayrilan_sayisi() - ayrilan_once);
    gozle("opak kayit", "cagiran", tampon);
    cagiran++;

    once = fark();
    char *kopya = kayit_ad_kopyasi(k);
    snprintf(tampon, sizeof tampon, "kopya %s", kopya != NULL ? kopya : "yok");
    say_birak(kopya);
    snprintf(tampon + strlen(tampon), sizeof tampon - strlen(tampon), ", sayac farki %d", fark() - once);
    gozle("donen kopya", "cagiran", tampon);
    cagiran++;

    once = fark();
    const char *odunc = kayit_ad(k);
    snprintf(tampon, sizeof tampon, "ad %s, sayac farki %d", odunc, fark() - once);
    gozle("odunc verilen ad", "cagrilan", tampon);
    cagrilan++;

    once = fark();
    Kayit *uzun = kayit_olustur("cok uzun bir kayit adi", 8);
    snprintf(tampon, sizeof tampon, "bos %d, sayac farki %d", uzun == NULL, fark() - once);
    gozle("hata yolunda temizlik", "cagrilan", tampon);
    cagrilan++;

    birakilan_once = birakilan_sayisi();
    kayit_yok_et(k);
    snprintf(tampon, sizeof tampon, "birakilan blok %d", birakilan_sayisi() - birakilan_once);
    gozle("kaydin ic tamponu", "cagrilan", tampon);
    cagrilan++;

    once = fark();
    int *kayip = say_ayir(sizeof *kayip);
    kayip = say_ayir(sizeof *kayip);
    say_birak(kayip);
    snprintf(tampon, sizeof tampon, "sayac farki %d", fark() - once);
    gozle("uzerine yazilan isaretci", "sahipsiz", tampon);
    sahipsiz++;

    Kayit *k2 = kayit_olustur("taslak", 16);
    once = fark();
    if (k2 != NULL) {
        kayit_ad_kopyasi(k2);
    }
    snprintf(tampon, sizeof tampon, "sayac farki %d", fark() - once);
    kayit_yok_et(k2);
    gozle("yok sayilan donus", "sahipsiz", tampon);
    sahipsiz++;

    printf("\n%-10s%d\n", "dil", dil);
    printf("%-10s%d\n", "cagiran", cagiran);
    printf("%-10s%d\n", "cagrilan", cagrilan);
    printf("%-10s%d\n", "sahipsiz", sahipsiz);
    printf("toplam gozlem: %d\n", dil + cagiran + cagrilan + sahipsiz);
    printf("ayrilan %d birakilan %d fark %d\n", ayrilan_sayisi(), birakilan_sayisi(), fark());
    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
yeniden boyutlandirma         cagiran   ilk eleman 7, sayac farki 0
opak kayit                    cagiran   bos degil 1, ayrilan blok 2
donen kopya                   cagiran   kopya defter, sayac farki 0
odunc verilen ad              cagrilan  ad defter, sayac farki 0
hata yolunda temizlik         cagrilan  bos 1, sayac farki 0
kaydin ic tamponu             cagrilan  birakilan blok 2
uzerine yazilan isaretci      sahipsiz  sayac farki 1
yok sayilan donus             sahipsiz  sayac farki 1

dil       4
cagiran   4
cagrilan  3
sahipsiz  2
toplam gozlem: 13
ayrilan 11 birakilan 9 fark 2
```

**IS1.** On üç gözlemin **4**'ü `dil` sınıfında, **4**'ü `çağıran`'da, **3**'ü `çağrılan`'da
ve **2**'si `sahipsiz`'de topladı. `dil` sınıfının dördünde de bir bırakma çağrısı yok —
yerel değişken, statik alan, dizgi değişmezi ve yerel dizi kendi ömrünü kendisi bitiriyor.
`ayrilan 11 birakilan 9 fark 2` satırı, koşumun sonunda hâlâ **iki** bloğun bırakılmadan
kaldığını söylüyor; bu iki blok, `sahipsiz` satırlarının bıraktığı fark ile birebir eşleşiyor
— üzerine yazılan işaretçi ve yok sayılan dönüş, ikisi de tam bir blok kaybetti. Bu koşum
**uyarısız** derlendi ve üç ayrı koşumda **birebir aynı** çıktıyı verdi; kursun geri kalan
her dersi bu dört sınıfı ve bu tabloyu kaynak alacak.

#### Adres Alma ve Dolaylama: `yerel degisken` Satırının Derinleştirilmesi

Çekirdeğin ilk satırı — `yerel degisken` — iki işlecin en sık kullanılan çiftini içeriyor:
adres alma (`&`) ve dolaylama (`*`). Bu ders onları çekirdeğin dışında, yalnız bu ikisine
odaklanan küçük bir koşumla ayrıca ölçüyor.

```c
// adres_alma.c — adres alma ve dolaylama, adres basmadan esitlik ve deger ile olculur
#include <stdio.h>

int main(void) {
    int sayi = 42;
    int *p = &sayi;

    printf("%s %d\n", "isaret edilen deger", *p);
    printf("%s %d\n", "p sayinin adresini mi gosteriyor", (int)(p == &sayi));

    *p = 100;
    printf("%s %d\n", "dolaylamayla yazilan yeni deger", sayi);

    int baska = 7;
    p = &baska;
    printf("%s %d\n", "yeniden atamadan sonra isaret edilen deger", *p);
    printf("%s %d\n", "p artik sayiyi mi gosteriyor", (int)(p == &sayi));
    return 0;
}
```

```
isaret edilen deger 42
p sayinin adresini mi gosteriyor 1
dolaylamayla yazilan yeni deger 100
yeniden atamadan sonra isaret edilen deger 7
p artik sayiyi mi gosteriyor 0
```

**IS2.** `&sayi` ifadesi `sayi` değişkeninin adresini üretiyor ve bu adres `p`'ye atanıyor;
`p == &sayi` karşılaştırması **1** veriyor, yani `p` gerçekten `sayi`'yi gösteriyor — ama bu
karşılaştırma adresin **kendisini** hiç yazdırmadan, yalnız iki adresin eşit olup olmadığını
soruyor. `*p = 100` satırı **dolaylama işlecini** bir atamanın sol tarafında kullanıyor:
`p`'nin gösterdiği yere yazıyor, `p`'nin kendisine değil. Bunun kanıtı bir sonraki satır —
`sayi` artık **100**; `p` üzerinden yapılan yazma, gösterdiği değişkeni gerçekten değiştirdi.
`p = &baska` ile `p` başka bir değişkeni göstermeye **yeniden atanabiliyor**; bu atamadan
sonra `*p` **7**'yi veriyor ve `p == &sayi` artık **0** — `p`'nin kendisi bir değişken,
istenildiğinde başka bir adresi tutmak üzere değiştirilebilir.

**IS3.** `int *p` bildirimi, `p`'nin **bir `int`'in adresini** tutacağını söylüyor — ama bu
tip, `p`'nin gösterdiği belleğin nasıl elde edildiğini hiç söylemiyor. Yukarıdaki koşumda
`p` önce bir yerel değişkeni (`sayi`), sonra bir başkasını (`baska`) gösterdi; ikisi de
`dil` sınıfındaydı ve kimse onları bırakmadı. Ama `int *` tipi, dinamik olarak ayrılmış bir
bloğu göstermek için de kullanılabilir — çekirdekteki `ayir ve birak` satırı tam olarak
bunu yaptı: `int *blok = say_ayir(...)`. Aynı bildirim, aynı işleçler (`*`, `&`), iki farklı
sahiplik sınıfı. Bu, dersin sınırlayıcı ölçümüdür.

#### İşaretçiye İşaretçi: Dolaylamanın İkinci Katmanı

Yukarıdaki `p` değişkeni bir `int`'i gösteriyordu; ama bir işaretçinin kendisi de bir
değişken olduğuna göre, onun adresini tutan başka bir işaretçi de yazılabilir. Bu, tek bir
`*` yerine iki tane kullanmayı gerektiriyor ve dolaylamayı bir kat daha derinleştiriyor.

```c
// cift_isaretci.c — bir isaretciye isaretci, dolaylamayi bir kat daha uygular
#include <stdio.h>

int main(void) {
    int sayi = 9;
    int *p = &sayi;
    int **pp = &p;

    printf("%s %d\n", "iki kat dolaylamayla okunan deger", **pp);
    printf("%s %d\n", "pp p'nin adresini mi gosteriyor", (int)(*pp == p));

    **pp = 30;
    printf("%s %d\n", "iki kat dolaylamayla yazilan yeni deger", sayi);

    int baska = 4;
    *pp = &baska;
    printf("%s %d\n", "pp uzerinden p'yi degistirince p'nin gosterdigi deger", *p);
    printf("%s %d\n", "p artik sayiyi mi gosteriyor", (int)(p == &sayi));
    return 0;
}
```

```
iki kat dolaylamayla okunan deger 9
pp p'nin adresini mi gosteriyor 1
iki kat dolaylamayla yazilan yeni deger 30
pp uzerinden p'yi degistirince p'nin gosterdigi deger 4
p artik sayiyi mi gosteriyor 0
```

**IS4.** `int **pp` bildirimi, `pp`'nin bir `int *`'in adresini tuttuğunu söylüyor —
`pp`, `p`'yi gösteriyor, `p` de `sayi`'yi gösteriyor. `**pp` iki dolaylama işlecini art
arda uyguluyor: önce `*pp` ile `p`'nin taşıdığı adrese ulaşılıyor, sonra ikinci `*` ile o
adresteki değere. `**pp = 30` satırı bu iki katmanlı yolu bir atamanın sol tarafında
kullanıyor ve `sayi` gerçekten **30** oluyor — tıpkı `*p = 100`'ün `sayi`'yi değiştirdiği
gibi, ama araya `p` giriyor. Son iki satır `*pp`'nin, yani `p`'nin kendisinin, yeniden
atanabildiğini gösteriyor: `*pp = &baska` ataması `p`'yi `baska`'yı göstermeye çeviriyor —
`pp` üzerinden, `p`'ye doğrudan erişmeden. Bu ayrım, bir işlevin çağıranın işaretçisini
kendisi değiştirmek istediği durumlarda önem kazanıyor: `int *` alan bir işlev yalnız
gösterilen değeri değiştirebilir, `int **` alan bir işlev çağıranın işaretçisini başka bir
adrese çevirebilir. Sınıf yine `dil`: üçü de yerel değişken, hiçbiri ayrılmadı.

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

Bir işaretçinin tipi, gösterdiği bloğun **kim tarafından bırakılacağını** söylemez. `int *`
bir yerel değişkeni de gösterebilir, `say_ayir` ile ayrılmış bir bloğu da — kaynakta ikisi
de aynı görünür ve derleyici aralarında hiçbir ayrım yapmaz. Yukarıdaki iki koşumda `p`
değişkeni üç farklı anda üç farklı şeyi gösterdi: bir yerel değişken, başka bir yerel
değişken ve (çekirdekte) ayrılmış bir blok. Üçünde de bildirim aynıydı (`int *`). Bir
işaretçiyi okuyan biri, tipine bakarak sahiplik sınıfını **çıkaramaz** — bu bilgi ancak
değişkenin nereden geldiğine (bir `&` ifadesinden mi, bir ayırma çağrısından mı) bakılarak
ya da onu belgeleyen sözleşme okunarak öğrenilebilir. Kursun geri kalanı, bu
sözleşmenin nasıl yazıldığını ve nasıl kırıldığını ölçüyor.

#### Özet

- Bir bellek bloğunun sahibi dört sınıftan biridir: **dil**, **çağıran**, **çağrılan**,
  **sahipsiz**; bu ders **dil** sınıfını çekirdeğin tam koşumuyla ölçüyor.
- Beş dosyalık ölçüm çekirdeği (`sayac.h`, `sayac.c`, `sahip.h`, `sahip.c`, `sahiplik.c`)
  on üç gözlemi **4 dil · 4 çağıran · 3 çağrılan · 2 sahipsiz** olarak dağıtıyor; toplam
  fark **2**.
- Adres alma (`&`) bir değişkenin adresini üretir; dolaylama (`*`) o adresteki değeri okur
  ya da ona yazar — ikisi de adres basılmadan, eşitlik ve değerle ölçülebilir.
- Bir işaretçi yeniden atanabilir ve farklı zamanlarda farklı değişkenleri gösterebilir;
  bu, işaretçinin kendisiyle gösterdiği değer arasındaki farktır.
- Bir işaretçinin tipi, gösterdiği bloğun **kim tarafından bırakılacağını** söylemez —
  aynı `int *` bildirimi hem bir yerel değişkeni hem ayrılmış bir bloğu gösterebilir.

#### Sonraki Adım

Bu derste ölçülen `yerel dizi` satırı, `son - ilk` farkını **3** olarak verdi ama bu farkın
nasıl hesaplandığı üzerinde durulmadı. Bir sonraki ders, İşaretçi Aritmetiği, bu farkın neden
bayt cinsinden değil **eleman cinsinden** olduğunu ve bir işaretçiyi artırmanın tip boyutuna
göre nasıl ilerlediğini ölçüyor — hâlâ `dil` sınıfında kalarak, ama artık tek bir değere
değil bir dizinin elemanları arasındaki ilişkiye bakarak.

### İşaretçi Aritmetiği

Source: https://academia.sh/tr/kurslar/c-bellek/isaretci-aritmetigi

Bir işaretçiyi artırmanın, azaltmanın ve iki işaretçiyi çıkarmanın neden bayt cinsinden değil eleman cinsinden işlediği; sınır dışına taşan bir aritmetiğin neden hiç koşulmadığı.

Önceki ders `yerel dizi` satırında `son - ilk` farkını **3** olarak verdi: dört elemanlı
bir dizide ilk ve son eleman arasında üç adım var. Ama bu fark bayt cinsinden değil —
`son` ve `ilk` arasındaki gerçek bellek uzaklığı, dizinin eleman tipi kaç bayt tutuyorsa
onun katıdır. Bu ders o katsayının nereden geldiğini ve bir işaretçiyi artırıp
azaltmanın ne anlama geldiğini ölçüyor. Sınıf hâlâ **dil**: bir dizinin elemanları
arasında gezinmek, herhangi bir ayırma ya da bırakma çağrısı gerektirmiyor.

#### Artırma ve Azaltma: Tip Boyutuna Göre İlerleme

```c
// artirma.c — bir isaretciyi artirmak, tip boyutu kadar ilerler
#include <stdio.h>

int main(void) {
    int dizi[5] = {10, 20, 30, 40, 50};
    int *p = dizi;

    printf("%s %d\n", "ilk eleman", *p);
    p++;
    printf("%s %d\n", "bir artirmadan sonraki eleman", *p);
    p += 2;
    printf("%s %d\n", "uc artirmadan sonraki eleman", *p);
    p--;
    printf("%s %d\n", "bir azaltmadan sonraki eleman", *p);

    double cift[3] = {1.5, 2.5, 3.5};
    double *d = cift;
    d++;
    printf("%s %.1f\n", "double dizide bir artirmadan sonraki eleman", *d);
    return 0;
}
```

```
ilk eleman 10
bir artirmadan sonraki eleman 20
uc artirmadan sonraki eleman 40
bir azaltmadan sonraki eleman 30
double dizide bir artirmadan sonraki eleman 2.5
```

**IS5.** `p++` işaretçiyi bir sonraki `int`'e taşıyor — yazılan adım her zaman **bir
eleman**, bir bayt değil. `p += 2` iki elemanı birden atlıyor: indeks 1'den indeks 3'e.
`p--` bir eleman geri dönüyor. `double cift` dizisinde de aynı `p++` yazımı kullanıldı
ve yine tam olarak **bir eleman** ilerledi — `int` ve `double` farklı boyutlarda olsa
bile, artırma işleci her ikisinde de "bir sonraki eleman" anlamına geliyor. Boyutun
kendisi (`sizeof`) hiçbir yerde basılmadı; ölçüm yalnız işaret edilen **değer** üzerinden
yapıldı.

#### İki İşaretçinin Farkı ve Sırası

```c
// fark_ve_sira.c — iki isaretcinin farki eleman biriminde, sirasi karsilastirma ile olculur
#include <stdio.h>

int main(void) {
    int dizi[6] = {1, 2, 3, 4, 5, 6};
    int *ilk = &dizi[0];
    int *orta = &dizi[3];
    int *son = &dizi[5];

    printf("%s %d\n", "orta eksi ilk", (int)(orta - ilk));
    printf("%s %d\n", "son eksi orta", (int)(son - orta));
    printf("%s %d\n", "ilk once orta mi geliyor", (int)(ilk < orta));
    printf("%s %d\n", "son once orta mi geliyor", (int)(son < orta));

    int *gezici = ilk;
    int adim = 0;
    while (gezici < son) {
        gezici++;
        adim++;
    }
    printf("%s %d\n", "ilkten sona kadar atilan adim", adim);
    return 0;
}
```

```
orta eksi ilk 3
son eksi orta 2
ilk once orta mi geliyor 1
son once orta mi geliyor 0
ilkten sona kadar atilan adim 5
```

**IS6.** `orta - ilk` **3** veriyor: `dizi[3]` ile `dizi[0]` arasında üç eleman aralığı
var. Bu çıkarma işleminin sonucu her zaman **eleman sayısı**dır, iki adresin bayt
farkı değil — `orta - ilk` ifadesi `3` yazdırıyor, adreslerin kendisi hiç görünmüyor.
`ilk < orta` karşılaştırması **1** veriyor çünkü `ilk`, dizide `orta`'dan önce duruyor;
bu sıralama, `<` işlecinin aynı dizi içindeki iki işaretçi için tanımlı olmasından geliyor.
`gezici < son` koşuluyla kurulan döngü, `ilk`'ten `son`'a kadar tam **5** adımda
ilerliyor — bu da `son - ilk` farkıyla birebir eşleşiyor.

#### `char` Dizisinde Aritmetik: Eleman Adımı Bayt Adımına Eşitken

Önceki iki örnekte `int` ve `double` dizileri kullanıldı; her ikisinde de bir artırma
birden fazla bayt ilerledi, ama bu ilerleme hiç ölçülmedi — yalnız işaret edilen değer
okundu. `char` tipi burada özel bir durum oluşturuyor: `sizeof(char)` her zaman **1**'dir,
bu yüzden bir `char *`'ı artırmak, eleman cinsinden bir adım ile bayt cinsinden bir adımı
aynı anda karşılıyor. Bu, bir dizginin karakterleri arasında gezinirken sıkça karşılaşılan
bir durum.

```c
// karakter_gezinme.c — char isaretcisinde bir artirma, bir sonraki karaktere tasir
#include <stdio.h>

int main(void) {
    char dizgi[] = "abcd";
    char *harf = dizgi;

    printf("%s %c\n", "ilk karakter", *harf);
    harf++;
    printf("%s %c\n", "bir artirmadan sonraki karakter", *harf);

    char *son_harf = &dizgi[3];
    printf("%s %d\n", "son harf eksi ilk harf", (int)(son_harf - dizgi));
    printf("%s %c\n", "son karakterin degeri", *son_harf);
    return 0;
}
```

```
ilk karakter a
bir artirmadan sonraki karakter b
son harf eksi ilk harf 3
son karakterin degeri d
```

**IS7.** `harf++` çağrısı `dizgi[0]`'dan `dizgi[1]`'e geçiyor; okunan değer `'a'`'dan
`'b'`'ye değişiyor. `son_harf - dizgi` farkı **3** veriyor — `dizgi[3]`, dizinin
başlangıcından üç eleman uzakta. Bu fark, `int` dizisindeki `orta - ilk` örneğiyle aynı
kuralı izliyor: sonuç eleman cinsinden. Farkın `char` için aynı zamanda bayt cinsinden bir
uzaklığa da eşit olması, `sizeof(char)`'ın **1** olmasının doğrudan sonucu — bu, kuralın
bir istisnası değil, kuralın kendisinin `char` özelinde aldığı biçim.

#### İki Boyutlu Dizide Satır Adımı

Buraya kadarki üç örnek tek boyutlu diziler kullandı; artırma her zaman **bir eleman**
ilerledi. İki boyutlu bir dizide bu kural değişmiyor, ama "eleman" tanımı değişiyor:
`int matris[3][4]` üzerinde bir satır işaretçisi (`int (*)[4]`) tanımlandığında, bir
artırma tek bir `int` değil, **tüm bir satırı** (4 `int`) atlıyor.

```c
// satir_adimi.c — iki boyutlu bir dizide satir isaretcisi tum satiri bir adim sayar
#include <stdio.h>

int main(void) {
    int matris[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    int (*satir)[4] = matris;

    printf("%s %d\n", "ilk satirin ilk elemani", satir[0][0]);
    satir++;
    printf("%s %d\n", "bir artirmadan sonraki satirin ilk elemani", satir[0][0]);
    printf("%s %d\n", "ayni satirin ucuncu elemani", satir[0][2]);

    int *duz = &matris[0][0];
    int *ikinci_satir_basi = &matris[1][0];
    printf("%s %d\n", "ikinci satir basi eksi ilk eleman", (int)(ikinci_satir_basi - duz));
    return 0;
}
```

```
ilk satirin ilk elemani 1
bir artirmadan sonraki satirin ilk elemani 5
ayni satirin ucuncu elemani 7
ikinci satir basi eksi ilk eleman 4
```

**IS8.** `satir` değişkeni `int (*)[4]` tipinde — "dört `int`'lik bir dizinin
adresi" tipini taşıyor, düz bir `int *` değil. `satir++` çağrıldığında işaretçi bir
sonraki **satıra** geçiyor: `satir[0][0]` değeri `1`'den `5`'e atlıyor, yani `matris`'in
ikinci satırının başına. Bu, `artirma.c`'deki `p++`'ın bir `int` kadar ilerlemesiyle aynı
kuralın farklı bir tip üzerindeki görünümü: adım her zaman **işaret edilen tipin
boyutu** kadardır, burada o tip tek bir `int` değil dört `int`'lik bir satır. Düz bir
`int *` (`duz`) ile ikinci satırın başı arasındaki fark **4** çıkıyor — bu da `int
matris[3][4]`'ün bir işleve geçtiğinde neden `int **` değil `int (*)[4]`'e bozunduğunu
doğruluyor: satır boyutu (4) tipin bir parçası olarak taşınıyor, aksi hâlde `satir++`
doğru elemana ilerleyemezdi.

#### Dinamik Ayrılmış Bir Dizide Aynı Kural

Buraya kadarki üç örnek yerel (`dil` sınıfı) dizilerde çalıştı. Aynı aritmetik kuralları,
dinamik olarak ayrılmış bir blok üzerinde de **birebir** geçerli — tip boyutuna göre
ilerleme, işaretçinin dizinin nereden geldiğini bilmesini gerektirmiyor.

```c
// dinamik_artirma.c — dinamik ayrilan bir blokta isaretci aritmetigi ayni kurali izler
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int uzunluk = 4;
    int *dizi = malloc((size_t)uzunluk * sizeof *dizi);
    if (dizi == NULL) {
        printf("%s\n", "ayirma basarisiz");
        return 1;
    }
    for (int i = 0; i < uzunluk; i++) {
        dizi[i] = (i + 1) * 10;
    }

    int *p = dizi;
    printf("%s %d\n", "ilk eleman", *p);
    p += 2;
    printf("%s %d\n", "iki artirmadan sonraki eleman", *p);

    int *son = dizi + uzunluk - 1;
    printf("%s %d\n", "son eksi ilk", (int)(son - dizi));

    free(dizi);
    dizi = NULL;
    return 0;
}
```

```
ilk eleman 10
iki artirmadan sonraki eleman 30
son eksi ilk 3
```

**IS9.** `dizi`, `malloc` ile ayrılmış bir blok, ama `p += 2` çağrısı `artirma.c`
örneğindeki yerel diziyle **tamamen aynı sonucu** üretiyor: iki eleman atlıyor, `10`'dan
`30`'a. `son - dizi` farkı **3** — `fark_ve_sira.c`'deki `orta - ilk` örneğiyle aynı
kural. Aradaki fark aritmetikte değil, sahiplikte: bu blok `malloc` ile isteniyor, bu
yüzden sınıf burada **çağıran** — `dizi`'yi ayıran `main`, onu kullanan da `main`, ve
bırakma yükümlülüğü de `main`'de kalıyor. Aritmetiğin kendisi bu sahiplik bilgisinden
tamamen bağımsız: bir işaretçinin gösterdiği blok `dil` sınıfında yerel bir dizi olsun ya
da `çağıran` sınıfında dinamik bir blok olsun, `p++`'ın ne kadar ilerleyeceği yalnız
**tipe** bakar, belleğin nereden geldiğine bakmaz.

#### İndeksleme ile Aritmetiğin Denkliği

Buraya kadar `p[i]` yazımı ve `p + i` yazımı ayrı ayrı kullanıldı, ama aralarındaki
ilişki hiç ölçülmedi. C'de köşeli parantez indekslemesi, aritmetik üzerine tanımlı bir
**kısaltmadır**: `p[i]` ifadesi tam olarak `*(p + i)` anlamına geliyor, ne bir bayt
farklı ne de ayrı bir kural.

```c
// indeks_denkligi.c — p[i] yazimi ile *(p + i) yazimi ayni adresi gosterir
#include <stdio.h>

static int indeksle(int *p, int i) {
    return p[i];
}

static int dolaylayarak_topla(int *p, int i) {
    return *(p + i);
}

int main(void) {
    int dizi[5] = {100, 200, 300, 400, 500};
    int *p = dizi;

    int indeks_sonucu = indeksle(p, 3);
    int aritmetik_sonucu = dolaylayarak_topla(p, 3);
    printf("%s %d\n", "p uc indeksi", indeks_sonucu);
    printf("%s %d\n", "p artirilmis uc kere dolaylanmis", aritmetik_sonucu);
    printf("%s %d\n", "iki yazim ayni deger mi veriyor", (int)(indeks_sonucu == aritmetik_sonucu));

    int sifirinci_indeks = indeksle(p, 0);
    int sifirinci_aritmetik = dolaylayarak_topla(p, 0);
    printf("%s %d\n", "sifirinci indeks", sifirinci_indeks);
    printf("%s %d\n", "dolaylanmis p ile ayni mi", (int)(sifirinci_indeks == sifirinci_aritmetik));
    return 0;
}
```

```
p uc indeksi 400
p artirilmis uc kere dolaylanmis 400
iki yazim ayni deger mi veriyor 1
sifirinci indeks 100
dolaylanmis p ile ayni mi 1
```

**IS10.** `p[3]` ve `*(p + 3)` ifadeleri aynı değeri (**400**) veriyor ve eşitlik
karşılaştırması **1** çıkıyor — bu, iki yazımın derleyici için tamamen aynı işlemi ifade
ettiğini doğruluyor. `p[0]` ile `*p` arasındaki eşitlik de aynı kuralın bir uç durumu:
sıfırıncı indeks, hiç ilerlemeden doğrudan dolaylamakla özdeş. Bu denklik, bu dersteki
`p++`, `p += n` ve `son - ilk` ölçümlerinin hepsinin neden `dizi[i]` yazımıyla tutarlı
sonuç verdiğini açıklıyor: indeksleme, aritmetiğin üstüne kurulmuş bir gösterim
kolaylığı, ayrı bir mekanizma değil. Sınıf yine **dil**: hem `p[3]` hem `*(p + 3)` aynı
yerel diziyi gösteriyor, aradaki fark yalnız yazım tercihidir. Bu denklik aynı zamanda
şunu da açıklıyor: `dizi[i]` yazımı bile aslında bir işaretçi aritmetiğidir, çünkü `dizi`
bir işleve ya da bir işleç bağlamına girdiğinde ilk elemanının adresine bozunur ve
`dizi[i]`, tam olarak `*(dizi + i)`'ye eşdeğer hâle gelir.

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

Bir işaretçi aritmetiği, yalnız **aynı dizinin içinde ya da onun bir eleman ötesinde**
tanımlıdır — dizinin son elemanının bir ötesini göstermek (döngü sonlandırma için)
geçerlidir, ama o adresi **dolaylamak** ya da diziyi iki eleman aşarak ilerletmek
tanımsız davranıştır. Yukarıdaki `fark_ve_sira.c` örneğinde `gezici`, döngü sırasında
en fazla `son` adresine kadar ilerliyor ve orada duruyor — dizinin sınırını aşan bir
artırma hiç koşturulmuyor. Bu sınır, derleyicinin denetlediği bir şey değil: `p += 100`
yazmak da derlenir, ama sonucu tanımsızdır ve bu ders böyle bir çağrıyı hiç içermiyor.
Aritmetiğin geçerliliği, kodu yazanın dizinin sınırlarını bilmesine bağlıdır — tip
sistemi bu sınırı taşımaz.

Aynı sınır, `dizi` gibi statik ayrılmış bir bloğun **dışına** çıkan işaretçi değerleri
için de geçerli: `p - 1` ile dizinin başlangıcının bir öncesini göstermek, ya da
`son + 1`'den daha ileri gitmek, aynı tanımsız davranış sınıfına giriyor. Sınırın
"bir eleman ötesi" olarak tanımlanmasının nedeni tam olarak bu: döngü sonlandırma
koşulları (`gezici < son` gibi) o tek adrese güvenle ulaşabilsin diye standart bu
kadarını garanti ediyor, daha fazlasını değil.

Bu sınır, iki **ilgisiz** diziye ait işaretçiler arasında da geçerli: `int a[4]` ile
`int b[4]` ayrı ayrı tanımlanmış iki yerel diziyse, `&b[0] - &a[0]` derlenir ama sonucu
**tanımsızdır** — çıkarma işleci yalnız aynı dizi (ya da aynı dizinin bir eleman ötesi)
içindeki iki işaretçi için anlamlı bir eleman sayısı üretmeyi garanti ediyor; iki farklı
dizinin bellekte birbirine göre nerede durduğu derleyicinin kararına bırakılmış bir
ayrıntı ve program bu ayrıntıya güvenemez. Yukarıdaki `fark_ve_sira.c` ve
`dinamik_artirma.c` örneklerinin ikisi de bu kuralı dolaylı olarak gözetiyor: her ikisi
de farkı yalnız **tek bir dizinin ya da tek bir bloğun içindeki** iki işaretçi arasında
alıyor, `a` ile `b` gibi ilgisiz iki bloğu hiç karşılaştırmıyor.

#### Özet

- İşaretçi artırma ve azaltma (`p++`, `p--`, `p += n`) her zaman **tip boyutu kadar**
  ilerler; adım sayısı eleman cinsindendir, bayt cinsinden değildir.
- İki işaretçinin farkı (`son - ilk`) aralarındaki **eleman sayısını** verir; adresin
  kendisi hiç basılmaz.
- İki işaretçi `<`, `>` gibi karşılaştırma işleçleriyle **sıralanabilir** — aynı dizi
  içindeyse bu sıralama dizideki konum sırasıyla eşleşir.
- Dizi sınırının bir ötesini **göstermek** geçerlidir, ama o adresi **dolaylamak**
  tanımsız davranıştır; iki ilgisiz dizinin işaretçileri arasında fark almak da aynı
  sınıftandır — bu derste hiçbiri koşturulmadı.
- `p[i]` yazımı `*(p + i)`'nin bir kısaltmasıdır; iki yazım her zaman aynı adresi
  gösterir ve aynı değeri döndürür.
- Aritmetik kuralları, işaretçinin gösterdiği blok yerel bir dizi (`dil`) olsun ya da
  dinamik olarak ayrılmış bir blok (`çağıran`) olsun **değişmez**; adım yalnız tipe
  bakar, belleğin kaynağına bakmaz. Bu yüzden dersin ölçtüğü bellek **dil** sınıfındadır:
  kuralı belleğin sahibi değil, dilin kendisi koyar.

#### Sonraki Adım

Bir dizi adı, çoğu bağlamda kendiliğinden ilk elemanının adresine **bozunuyor** —
`int *ilk = dizi` yazımı bunu zaten kullandı ama üzerinde durmadı. Bir sonraki ders,
Diziler ve İşaretçiler, bu bozunmanın ne zaman gerçekleştiğini, bir dizinin bir
işleve nasıl (ve neden uzunluk bilgisi olmadan) geçtiğini ve `çağıran` sınıfına ilk
adımı — dinamik olarak ayrılmış bir dizinin sahipliğini — ölçüyor.

### Diziler ve İşaretçiler

Source: https://academia.sh/tr/kurslar/c-bellek/diziler-ve-isaretciler

Bir dizi adının bir işleve geçerken ilk elemanının adresine bozunması, uzunluk bilgisinin bu bozunmada neden kaybolduğu ve dinamik olarak ayrılmış bir dizinin sahipliğinin çağırana geçtiği.

İki ders önce çekirdekteki `ayir ve birak` satırı bir bloğu `say_ayir` ile ayırıp
`say_birak` ile bıraktı; sınıfı **çağıran**dı çünkü belleği isteyen taraf onu bırakmakla
yükümlüydü. Bu ders o satırı derinleştiriyor ve önce dizi adlarının işlevlere nasıl
geçtiğini ölçüyor — çünkü dinamik bir diziyi doğru bırakabilmek, önce onun bir işleve
**ne olarak** ulaştığını bilmeyi gerektiriyor.

#### Bozunma: Dizi Adı İşleve Geçerken

```c
// bozunma.c — bir dizi adi islev parametresinde ilk elemanin adresine bozunur
#include <stdio.h>

static void goster(int *p, int uzunluk) {
    int toplam = 0;
    for (int i = 0; i < uzunluk; i++) {
        toplam += p[i];
    }
    printf("%s %d\n", "islev icinde toplam", toplam);
    printf("%s %d\n", "islev icinde ilk eleman", *p);
}

int main(void) {
    int dizi[4] = {2, 4, 6, 8};

    printf("%s %d\n", "dizi disinda ilk eleman", *dizi);
    printf("%s %d\n", "dizi ile ilk elemanin adresi esit mi", (int)(dizi == &dizi[0]));

    goster(dizi, 4);

    int *isaretci = dizi;
    printf("%s %d\n", "isaretciye atanan dizi ile ilk eleman esit mi", (int)(isaretci == &dizi[0]));
    return 0;
}
```

```
dizi disinda ilk eleman 2
dizi ile ilk elemanin adresi esit mi 1
islev icinde toplam 20
islev icinde ilk eleman 2
isaretciye atanan dizi ile ilk eleman esit mi 1
```

**IS11.** `dizi == &dizi[0]` karşılaştırması **1** veriyor: bir dizi adı kullanıldığında,
o ad **ilk elemanının adresiyle aynı değeri** taşıyor. `goster(dizi, 4)` çağrısında
`dizi`, işlevin `int *p` parametresine bu adres olarak geçiyor — işlev içinde `p[i]`
yazımı `dizi[i]` ile aynı elemanlara ulaşıyor ve toplam **20** çıkıyor (2+4+6+8).
`isaretci = dizi` ataması da aynı şeyi gösteriyor: bir dizi adı bir işaretçi değişkenine
atandığında, dizinin kendisi değil, ilk elemanının adresi kopyalanıyor. Bu olguya
**bozunma** deniyor: dizi adı, bir işlev parametresinde ya da bir işaretçiye atamada
kendi tipini (dizi) kaybedip bir işaretçi tipine dönüşüyor.

#### Kaybolan Uzunluk

```c
// uzunluk_kaybi.c — bozunma, dizinin eleman sayisi bilgisini tasimaz
#include <stdio.h>

static int topla(int *p, int uzunluk) {
    int toplam = 0;
    for (int i = 0; i < uzunluk; i++) {
        toplam += p[i];
    }
    return toplam;
}

int main(void) {
    int kucuk[3] = {1, 1, 1};
    int buyuk[7] = {1, 1, 1, 1, 1, 1, 1};

    printf("%s %d\n", "kucuk dizi toplami", topla(kucuk, 3));
    printf("%s %d\n", "buyuk dizi toplami", topla(buyuk, 7));
    printf("%s %d\n", "buyuk diziye yanlis uzunluk verilirse toplam", topla(buyuk, 3));
    return 0;
}
```

```
kucuk dizi toplami 3
buyuk dizi toplami 7
buyuk diziye yanlis uzunluk verilirse toplam 3
```

**IS12.** `topla` işlevi, kendisine geçen işaretçiden dizinin kaç eleman uzunluğunda
olduğunu **hiçbir şekilde** çıkaramıyor — bu yüzden `uzunluk` ayrı bir parametre olarak
taşınıyor. Son çağrıda `buyuk` dizisi (7 eleman) işleve geçiyor ama `uzunluk` olarak
**3** veriliyor; işlev bu yanlış bilgiye güveniyor ve yalnız ilk üç elemanı topluyor,
sonuç **3** çıkıyor — dizinin gerçek boyutuyla hiçbir ilgisi yok. Bu, bozunmanın
sınırlayıcı sonucudur: bir dizi bir işleve geçtiğinde, uzunluk bilgisi kaynakta ayrı
bir değer olarak **elle taşınmak zorundadır**; işaretçinin kendisinden geri kazanılamaz.

Uzunluğu ayrı bir parametreyle taşımak, C'de karşılaşılan tek yöntem değil. Bir başka
yaygın çözüm, dizinin kendi içine bir **sonlandırıcı değer** yerleştirmektir — dizgilerin
sonundaki `'\0'` tam olarak bu işi görüyor: bir dizgi üzerinde gezinen bir döngü, uzunluğu
ayrı bir parametreden değil, sonlandırıcıya ulaşana kadar ilerleyerek öğreniyor. İki
yöntemin de ortak noktası şu: bozunma sonucu kaybolan bilgi, kaynakta **başka bir biçimde**
geri kazanılmak zorunda — ya bir sayı olarak yanında taşınır, ya da dizinin içine gömülen
bir işaretle bulunur. Üçüncü bir yol yok; işaretçinin kendisi bu bilgiyi hiçbir zaman
taşımaz.

#### Dinamik Bir Dizinin Sahipliği

```c
// dinamik_dizi.c — ayrilan bir dizinin sahibi onu isteyen taraftir
#include <stdio.h>
#include <stdlib.h>

static int *dizi_olustur(int uzunluk, int baslangic) {
    int *dizi = malloc((size_t)uzunluk * sizeof *dizi);
    if (dizi == NULL) {
        return NULL;
    }
    for (int i = 0; i < uzunluk; i++) {
        dizi[i] = baslangic + i;
    }
    return dizi;
}

int main(void) {
    int uzunluk = 5;
    int *dizi = dizi_olustur(uzunluk, 10);

    if (dizi == NULL) {
        printf("%s\n", "ayirma basarisiz");
        return 1;
    }

    int toplam = 0;
    for (int i = 0; i < uzunluk; i++) {
        toplam += dizi[i];
    }
    printf("%s %d\n", "dinamik dizi toplami", toplam);
    printf("%s %d\n", "son eleman", dizi[uzunluk - 1]);

    free(dizi);
    dizi = NULL;
    printf("%s %d\n", "birakildiktan sonra bos isaretci mi", (int)(dizi == NULL));
    return 0;
}
```

```
dinamik dizi toplami 60
son eleman 14
birakildiktan sonra bos isaretci mi 1
```

**IS13.** `dizi_olustur` bir dizi **ayırıyor** ama kendisi hiç **bırakmıyor** — bloğu
`main`'e döndürüyor. `main` içindeki toplam **60** (10+11+12+13+14) ve son eleman
**14**, dizinin doğru dolduğunu gösteriyor. Sahiplik burada işlev sınırını aşarak
geçiyor: `dizi_olustur` belleği ayırıyor, ama onu **isteyen** ve kullanan `main`
bırakmakla yükümlü — bu yüzden sınıf **çağıran**. `free(dizi)` çağrısından hemen sonra
`dizi = NULL` atanıyor ve `dizi == NULL` karşılaştırması **1** veriyor; bu, bırakılmış
bir işaretçinin bir sonraki dersin konusu olan tehlikeli durumunu önlüyor.

#### Yanlış Uzunluk Sınırın Neresine Kadar Sessiz Kalır

`uzunluk_kaybi.c` örneği yanlış bir uzunluğun eksik bir toplam verdiğini gösterdi, ama
bu yalnız "az sayma" durumuydu. Yanlış uzunluk, dizinin **gerçek sınırını aşan** bir
değer olduğunda ne olur? Aşağıdaki örnek bu soruyu, sınırın içinde kalan ve sınırı aşan
iki çağrıyı yan yana koyarak ölçüyor.

```c
// sinir_asimi.c — dogru uzunluk sinirin icinde kalir, yanlis uzunluk disina tasabilir
#include <stdio.h>

static int en_buyuk(const int *p, int uzunluk) {
    int buyuk = p[0];
    for (int i = 1; i < uzunluk; i++) {
        if (p[i] > buyuk) {
            buyuk = p[i];
        }
    }
    return buyuk;
}

int main(void) {
    int dizi[4] = {3, 2, 9, 7};

    printf("%s %d\n", "dogru uzunlukla en buyuk", en_buyuk(dizi, 4));
    printf("%s %d\n", "eksik uzunlukla en buyuk", en_buyuk(dizi, 2));
    return 0;
}
```

```
dogru uzunlukla en buyuk 9
eksik uzunlukla en buyuk 3
```

**IS14.** Doğru uzunlukla (`4`) çağrıldığında `en_buyuk` dizinin tamamını tarıyor ve
**9**'u buluyor. Eksik bir uzunlukla (`2`) çağrıldığında ise yalnız ilk iki elemana
bakıyor ve **3**'ü döndürüyor — `9` değeri dizide var olmasına rağmen üçüncü sırada
kaldığı için hiç görülmüyor. Bu ders
kasıtlı olarak yalnız **eksik** uzunluk çağrısını içeriyor; gerçek uzunluğu **aşan** bir
değer verilmesi (`en_buyuk(dizi, 6)` gibi) dizinin sonundan sonraki belleği okumaya
çalışır ve bu tanımsız davranıştır — bu yüzden burada hiç koşturulmuyor. İki durumun
ortak noktası şu: `en_buyuk` işlevi kendisine geçen `uzunluk` değerine **koşulsuz
güveniyor**; ne eksik ne fazla bir değeri kendi başına tespit edemiyor, çünkü elindeki
tek bilgi bir başlangıç adresi ve bir sayı.

#### Uzunluğu da Birlikte Döndürmek

`dizi_olustur` uzunluğu `main`'in zaten bildiği bir değer olarak aldı ve kendisi hiç
hesaplamadı. Bir işlev, ayırdığı dizinin uzunluğunu **kendisi belirliyorsa** — örneğin bir
filtreleme sonucu, girişten daha kısa bir dizi üretiyorsa — bu uzunluğu çağırana nasıl
bildirir? İşaretçinin kendisi bunu taşımadığına göre, tek yol bir **çıkış parametresi**.

```c
// ciktisi_da_donen.c — ayiran islev, uzunlugu bir cikis parametresiyle geri bildirir
#include <stdio.h>
#include <stdlib.h>

static int *ciftleri_sec(const int *kaynak, int uzunluk, int *sonuc_uzunlugu) {
    int *sonuc = malloc((size_t)uzunluk * sizeof *sonuc);
    if (sonuc == NULL) {
        *sonuc_uzunlugu = 0;
        return NULL;
    }
    int sayac = 0;
    for (int i = 0; i < uzunluk; i++) {
        if (kaynak[i] % 2 == 0) {
            sonuc[sayac] = kaynak[i];
            sayac++;
        }
    }
    *sonuc_uzunlugu = sayac;
    return sonuc;
}

int main(void) {
    int kaynak[6] = {1, 2, 3, 4, 5, 6};
    int uzunluk = 0;

    int *ciftler = ciftleri_sec(kaynak, 6, &uzunluk);
    printf("%s %d\n", "secilen eleman sayisi", uzunluk);

    int toplam = 0;
    for (int i = 0; i < uzunluk; i++) {
        toplam += ciftler[i];
    }
    printf("%s %d\n", "secilenlerin toplami", toplam);

    free(ciftler);
    ciftler = NULL;
    return 0;
}
```

```
secilen eleman sayisi 3
secilenlerin toplami 12
```

**IS15.** `ciftleri_sec` kaynak dizinin **en fazla** `uzunluk` kadar eleman
üretebileceğini biliyor, o yüzden bloğu o boyutta ayırıyor, ama gerçekte kaç elemanın
çift olduğunu ancak taradıktan sonra öğreniyor — bu bilgiyi `*sonuc_uzunlugu` üzerinden
çağırana **geri yazıyor**. `main` bu çağrıdan sonra hem yeni diziyi (`ciftler`) hem onun
gerçek uzunluğunu (`uzunluk`, artık **3**) elinde buluyor; toplam **12** (2+4+6) bu ikisinin
doğru eşleştiğini kanıtlıyor. Sahiplik yine `dizi_olustur` örneğindeki gibi **çağıran**da:
`ciftleri_sec` belleği ayırıyor ama hiç bırakmıyor, `main` onu kullanıp `free` ile
bırakıyor. Bu örnek `dizi_olustur`'dan farklı olarak, uzunluğun **girişten** değil
**işlevin kendi hesabından** geldiği durumu ölçüyor — bu yüzden uzunluk artık bir giriş
parametresi değil, bir çıkış parametresi. `sonuc_uzunlugu`'nun kendisi bir `int *`
olarak geçiyor — bu, işaretçilerin yalnız dizileri değil, tek bir değeri "çağırana geri
yazmak" için de kullanılabildiğini gösteren ayrı bir kullanım biçimi.

#### İki Boyutlu Bir Dizinin Bozunması

Tek boyutlu bir dizi bir işleve geçtiğinde `int *`'a bozunuyordu. İki boyutlu bir dizi
geçtiğinde bozunma **bir düzey eksik** işliyor: `int matris[3][4]` bir işlev
parametresinde `int **` olmaz, satır boyutu (4 eleman) tipin bir parçası olarak kalır ve
`int (*)[4]` tipine bozunur.

```c
// matris_bozunmasi.c — int matris[3][4] bir isleve int (*)[4] olarak gecer, int ** olmaz
#include <stdio.h>

static int satir_toplami(int satir[4]) {
    int toplam = 0;
    for (int i = 0; i < 4; i++) {
        toplam += satir[i];
    }
    return toplam;
}

static int tum_toplam(int (*matris)[4], int satir_sayisi) {
    int toplam = 0;
    for (int i = 0; i < satir_sayisi; i++) {
        toplam += satir_toplami(matris[i]);
    }
    return toplam;
}

int main(void) {
    int matris[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    printf("%s %d\n", "ilk satirin toplami", satir_toplami(matris[0]));
    printf("%s %d\n", "tum matrisin toplami", tum_toplam(matris, 3));
    return 0;
}
```

```
ilk satirin toplami 10
tum matrisin toplami 78
```

**IS16.** `tum_toplam` işlevinin parametresi `int (*matris)[4]` —
"dört elemanlık bir dizinin adresi" tipini taşıyor. `matris[i]` yazımı burada `*(matris +
i)` anlamına geliyor ve bu, `i`'inci **satırın** ilk elemanının adresini veriyor; o adres
`satir_toplami`'ye bir kez daha bozunarak (bu kez `int *` olarak) geçiyor. `ilk satirin
toplami` **10** (1+2+3+4), `tum matrisin toplami` **78** (1'den 12'ye kadar tüm
elemanların toplamı) — iki katmanlı bozunmanın doğru elemanlara ulaştığını kanıtlıyor.
Eğer parametre `int **matris` olarak yazılsaydı, derleyici bunu **kabul etmezdi**: `int
(*)[4]` ile `int **` aynı boyutta (bir adres) olsalar da farklı tiplerdir, çünkü ilki
"satır boyutu 4 olan bir dizinin adresi" bilgisini taşırken ikincisi yalnız "bir
işaretçiye işaretçi" demektir — bu bilgi kaybı, derleyicinin `matris[i][j]` erişimini
doğru satıra yönlendirebilmesi için tam olarak gerekli olan bilgidir.

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

Bozunma yalnız **tek düzeyde** işliyor: bir `int dizi[4]` bir işleve geçtiğinde `int *`
olur, ama bir `int matris[3][4]` geçtiğinde `int **` **olmaz** — yukarıdaki
`matris_bozunmasi.c` örneği bunu satır toplamları üzerinden zaten ölçtü. Bu ders çok
boyutlu diziye burada duruyor; sabitlenen kural şu: bozunma her zaman **bir adres**
üretir ve o adresin "kaç elemanlık bir bloğu gösterdiği" bilgisini asla taşımaz. Bu bilgi
ya bir ayrı parametreyle (yukarıdaki `uzunluk` gibi) ya da bir sonlandırıcı değerle
(dizgi sonundaki `'\0'` gibi) kaynakta açıkça taşınmalıdır. `matris_bozunmasi.c`'de bu
bilgi üçüncü bir yoldan taşındı: satır boyutu (**4**) tipin kendisine (`int (*)[4]`)
gömülü kaldığı için ayrı bir parametre gerekmedi, ama satır **sayısı** (**3**) yine de
`tum_toplam`'a ayrı bir `satir_sayisi` parametresiyle geçirilmek zorunda kaldı — çünkü
tip yalnız sütun boyutunu taşıyor, satır sayısını taşımıyor.

#### Özet

- Bir dizi adı, bir işleve parametre olarak geçtiğinde ya da bir işaretçiye atandığında
  ilk elemanının adresine **bozunur**; `dizi == &dizi[0]` bu bozunmayı kanıtlıyor.
- Bozunma, dizinin **eleman sayısı bilgisini taşımaz** — yanlış bir uzunluk parametresi
  verildiğinde işlev bunu fark edemiyor, yalnız verilen sayı kadar elemana erişiyor.
- Dinamik olarak ayrılmış bir dizinin sahibi, onu **isteyen ve kullanan taraftır**
  (sınıf **çağıran**); ayıran işlev bloğu geri döndürüp bırakma yükümlülüğünü
  devredebilir.
- Bir bloğu bıraktıktan hemen sonra işaretçiye `NULL` atamak, o işaretçinin artık
  geçerli bir bloğu göstermediğini kaynakta açıkça işaretliyor.
- Çok elemanlı bir bloğun uzunluğu, işaretçinin kendisinden **geri kazanılamaz**; ayrı
  bir değer, bir çıkış parametresi ya da bir sonlandırıcı ile taşınmak zorundadır —
  uzunluk girişten geliyorsa giriş parametresi, işlev onu kendisi hesaplıyorsa çıkış
  parametresi kullanılır, ama sahiplik her iki durumda da `çağıran`'da kalır.
- İki boyutlu bir dizi bir işleve geçtiğinde `int (*)[N]` tipine bozunur, `int **`'a
  değil — satır boyutu tipin bir parçası olarak taşınır, satır sayısı taşınmaz.

Bu dersteki üç yeni ölçüm de aynı gözleme farklı açılardan bağlanıyor: bir dizinin
işaretçiye bozunması, dizinin **kendisi** hakkında hiçbir bilgi taşımıyor — ne
uzunluğunu, ne satır sayısını, ne de nereye kadar okumanın güvenli olduğunu. Bu bilgi
her zaman ayrı bir kanaldan gelmek zorunda: ya bir parametre olarak (`en_buyuk`'teki
`uzunluk`), ya tipin kendisine gömülü olarak (`matris_bozunmasi.c`'deki `int (*)[4]`),
ya da bir çıkış parametresi olarak (`ciktisi_da_donen.c`'deki `sonuc_uzunlugu`). Üçü de
aynı sınırın farklı çözümleri; hiçbiri sınırı ortadan kaldırmıyor, yalnız onu görünür ve
taşınabilir kılıyor.

#### Sonraki Adım

Önceki örnekte `free(dizi)` çağrısından hemen sonra `dizi`'ye `NULL` atandı — ama bu
adım atlanırsa ne olur? Bir sonraki ders, Boş ve Sarkan İşaretçiler, bırakılmış bir
bloğu hâlâ gösteren bir işaretçinin ne anlama geldiğini, bu durumun neden hiç
koşturulmadığını ve `NULL` atamanın neden onu **sahipsiz** sınıfından çıkarıp
denetlenebilir bir duruma getirdiğini ölçüyor.

### Boş ve Sarkan İşaretçiler

Source: https://academia.sh/tr/kurslar/c-bellek/bos-ve-sarkan-isaretciler

Boş işaretçinin denetlenebilir bir durum olduğu, sarkan bir işaretçinin ise bırakılmış bir bloğu göstermeye devam ettiği ve bu yüzden hiç koşturulmadığı; ikisinin de sahipsiz sınıfının iki farklı yüzü olduğu.

Önceki dersin son satırı bir bırakma çağrısından hemen sonra işaretçiye `NULL` atadı ve
bunu "bir sonraki dersin konusu" diye bıraktı. Bu ders o atamanın neden zorunlu bir
alışkanlık olduğunu ölçüyor: bırakılmış bir bloğu göstermeye devam eden bir işaretçi
**sarkan** işaretçi olur ve bu durum çekirdeğin `sahipsiz` sınıfına giren iki
gözlemden biridir — kimse bu işaretçiyi geçerli tutmakla yükümlü değildir, ama kimse de
onu `NULL`'a çekmeyi garanti etmez.

#### Boş İşaretçi: Denetlenebilir Bir Durum

```c
// bos_isaretci.c — bos isaretci, dolaylamadan once denetlenerek guvenli hale gelir
#include <stdio.h>
#include <stdlib.h>

static int *belki_ayir(int basarili) {
    if (!basarili) {
        return NULL;
    }
    int *p = malloc(sizeof *p);
    if (p != NULL) {
        *p = 9;
    }
    return p;
}

int main(void) {
    int *basarisiz = belki_ayir(0);
    printf("%s %d\n", "basarisiz cagri bos mu", (int)(basarisiz == NULL));

    int *basarili = belki_ayir(1);
    if (basarili != NULL) {
        printf("%s %d\n", "basarili cagridan gelen deger", *basarili);
    } else {
        printf("%s\n", "basarili cagri beklenmedik sekilde bos");
    }

    free(basarili);
    return 0;
}
```

```
basarisiz cagri bos mu 1
basarili cagridan gelen deger 9
```

**IS17.** `belki_ayir(0)` çağrısı `NULL` döndürüyor ve `basarisiz == NULL` karşılaştırması
**1** veriyor — bu, boş işaretçinin **beklenen ve denetlenebilir** bir durum olduğunu
gösteriyor. `belki_ayir(1)` çağrısı gerçek bir blok döndürüyor; ama bu blok da
kullanılmadan önce `basarili != NULL` ile denetleniyor, çünkü `malloc` her koşulda
başarılı olacağının garantisini vermiyor. Boş bir işaretçiyi **dolaylamadan önce**
denetlemek, bu ölçümün tek kuralı: `*basarisiz` hiç yazılmadı, çünkü `basarisiz`'in
boş olduğu zaten biliniyordu.

#### Sarkan İşaretçi: Bırakılmış Bir Bloğu Göstermeye Devam Etmek

Bir işaretçi `free` ile bırakıldıktan sonra, o işaretçinin taşıdığı değer **değişmiyor** —
hâlâ aynı bloğu gösteriyor, ama artık o blok geçerli değil. Bu duruma **sarkan işaretçi**
deniyor. Sarkan bir işaretçiyi dolaylamak (`*p` ile okumak ya da yazmak) ya da onu tekrar
`free`'ye vermek (çift bırakma) tanımsız davranıştır: sonuç çoğu zaman sessizce yanlış bir
değer, bazen bir çökme, hiçbir zaman güvenilir bir davranıştır. Bu yüzden bu ders **hiçbir
sarkan işaretçiyi dolaylamıyor ya da tekrar bırakmıyor** — yalnız bunun neden tehlikeli
olduğunu betimliyor ve tanımlı karşılığını çalıştırıyor.

```c
// tanimli_karsilik.c — birakma sonrasi NULL atamasi, sarkan durumu denetlenebilir kilar
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *p = malloc(sizeof *p);
    if (p == NULL) {
        printf("%s\n", "ayirma basarisiz");
        return 1;
    }
    *p = 21;
    printf("%s %d\n", "birakmadan once deger", *p);

    free(p);
    p = NULL;
    printf("%s %d\n", "birakmadan sonra bos mu", (int)(p == NULL));

    if (p != NULL) {
        printf("%s %d\n", "hic ulasilmayan dal", *p);
    } else {
        printf("%s\n", "dolaylama denetim sayesinde atlandi");
    }
    return 0;
}
```

```
birakmadan once deger 21
birakmadan sonra bos mu 1
dolaylama denetim sayesinde atlandi
```

**IS18.** `free(p)` çağrısından **hemen sonra** `p = NULL` atanıyor; bu iki satır arasına
hiçbir başka işlem girmiyor. `p == NULL` karşılaştırması **1** veriyor ve son `if`
bloğunda `p != NULL` **yanlış** olduğu için `*p` satırına hiç ulaşılmıyor — "dolaylama
denetim sayesinde atlandi" satırı bunun kanıtı. Eğer `p = NULL` ataması atlanmış olsaydı,
`p` bırakılmış bloğu göstermeye devam eden bir **sarkan işaretçi** olurdu ve `p != NULL`
denetimi onu yakalayamazdı — çünkü sarkan bir işaretçinin değeri boş değildir, yalnızca
geçersizdir. Bu fark, boş işaretçiyle sarkan işaretçi arasındaki temel ayrımdır: biri
denetlenebilir bir **değer** (boş), diğeri denetlenemeyen bir **durumdur** (geçersiz ama
boş görünmeyen).

Sarkan bir işaretçinin ikinci bir tehlikesi, onu tekrar `free`'ye vermektir — bu duruma
**çift bırakma** deniyor. `free(p)` çağrısından sonra, eğer `p` hâlâ eski adresi
taşıyorsa, ikinci bir `free(p)` çağrısı aynı bloğu iki kez bırakmaya çalışır; bu da
tanımsız davranıştır ve genellikle ayırıcının kendi iç durumunu bozar. Yukarıdaki örnekte
bu ikinci çağrı hiç yazılmadı, çünkü `p = NULL` atamasından sonra `free(NULL)` çağırmak
zararsızdır — standart, boş bir işaretçiyi bırakmayı **hiçbir işlem yapmama** olarak
tanımlıyor. Bu, `NULL` atamasının ikinci bir yararı: yalnız dolaylamayı değil, olası bir
ikinci bırakma çağrısını da zararsız hale getiriyor.

```c
// guvenli_ikinci_cagri.c — free(NULL) standart tarafindan zararsiz tanimlanir
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *p = malloc(sizeof *p);
    if (p == NULL) {
        printf("%s\n", "ayirma basarisiz");
        return 1;
    }
    *p = 4;
    printf("%s %d\n", "birakmadan once deger", *p);

    free(p);
    p = NULL;

    free(p);
    printf("%s\n", "ikinci free NULL uzerinde calisti");
    return 0;
}
```

```
birakmadan once deger 4
ikinci free NULL uzerinde calisti
```

**IS19.** Bu kod `free(p)`'yi **iki kez** çağırıyor, ama aralarına `p = NULL`
satırı giriyor. İlk çağrı gerçek bloğu bırakıyor; ikinci çağrı `NULL` üzerinde
çalışıyor ve standart bunu açıkça "hiçbir işlem yapma" olarak tanımladığı için
program çökmeden "ikinci free NULL uzerinde calisti" satırına ulaşıyor. Eğer ikinci
satırdaki `p = NULL` ataması olmasaydı, bu aynı kod ikinci `free(p)` çağrısında aynı
bloğu **iki kez** bırakmaya çalışırdı — bu da yukarıda tanımlanan çift bırakma hatası
olurdu ve bu yüzden burada denenmiyor.

Bu derste `sahipsiz` sınıfının iki üyesi de aynı kökten geliyor: birinci dersteki
çekirdekte `uzerine yazilan isaretci` gözlemi, bir bloğun adresini tutan tek işaretçinin
**bırakılmadan** üzerine yazılmasıyla o bloğu erişilemez bırakmıştı — bu bir **sızıntı**.
Sarkan işaretçi ise tersi yönde bir hatadır: blok bırakılmıştır ama işaretçi hâlâ ona
işaret etmeye devam eder. İkisi de "kimse bu durumu garanti altına almadı" anlamına
geliyor, bu yüzden ikisi de `sahipsiz` sınıfında.

#### Birden Çok Kopya İşaretçi: NULL Ataması Neden Yetmez

Şimdiye kadarki iki örnek de tek bir işaretçi üzerinden ilerledi. Gerçek kodda aynı
bloğu birden çok işaretçi gösterebilir — biri bırakma çağrısını yapar, ama diğerleri
`p = NULL` satırından habersiz kalır.

```c
// kopya_isaretci.c — ayni blogu gosteren iki isaretciden yalniz biri NULL'a cekilir
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *p = malloc(sizeof *p);
    if (p == NULL) {
        printf("%s\n", "ayirma basarisiz");
        return 1;
    }
    *p = 7;

    int *q = p;
    printf("%s %d\n", "p ve q ayni degeri gosteriyor mu", (int)(*p == *q));
    printf("%s %d\n", "p ve q ayni adresi mi tasiyor", (int)(p == q));

    free(p);
    p = NULL;
    printf("%s %d\n", "p birakma sonrasi bos mu", (int)(p == NULL));
    return 0;
}
```

```
p ve q ayni degeri gosteriyor mu 1
p ve q ayni adresi mi tasiyor 1
p birakma sonrasi bos mu 1
```

**IS20.** `int *q = p;` satırından sonra `p` ve `q` **aynı adresi** taşıyor — hem
`*p == *q` hem `p == q` karşılaştırması **1** veriyor. `free(p)` ve ardından `p = NULL`
çalıştığında yalnız `p` değişkeninin **kendisi** etkileniyor; `q` bu iki satırda hiç
geçmiyor, dolayısıyla hâlâ `free` ile bırakılmış bloğun eski adresini taşıyor —
`q` şimdi bir **sarkan işaretçi**. Bu kod `q`'yu bu noktadan sonra ne dolaylıyor ne de
`NULL` ile karşılaştırıyor, çünkü `q`'nun taşıdığı değer artık geçersiz ve onu okumaya
çalışmak (bir karşılaştırma dahil) tanımsız davranıştır. Gösterilmek istenen şey kod
çalışırken **görülmüyor** — tam olarak da bu, sınırın kendisi: `p = NULL` yalnızca `p`
adlı değişkenin durumunu değiştirir, `q`'nun durumunu değil.

#### Sahiplik Ekseninde Sarkan İşaretçi: Üç Farklı Kaynak

Bu derse kadarki iki örnek de sarkan durumu tek bir kaynaktan üretti: bir `malloc`
bloğunun `free` ile bırakılması. Ama sarkan işaretçi, kursun dört sahiplik sınıfının
(`dil`, `çağıran`, `çağrılan`, `sahipsiz`) her birinde farklı bir yoldan ortaya
çıkabilir; kod burada çalıştırılmıyor çünkü her biri tanımsız davranışa yol açar, ama
üçü de bu dersin ölçtüğü aynı köke bağlanıyor.

`dil` sınıfında, bir işlev yerel bir değişkenin adresini döndürürse (`return &yerel;`
gibi), döndürülen işaretçi işlev geri döndüğü anda sarkan olur — çünkü `yerel`'in
ömrünü yöneten dilin kendisidir ve bu ömür işlevin gövdesiyle sınırlıdır. Burada hiçbir
`free` çağrısı yok; blok bir `malloc`'la değil, çağrı çerçevesinin
kendisiyle ortadan kalkıyor. Sonuç yine de aynı: çağıran taraf geçersiz bir adresi
gösteren bir işaretçiyle baş başa kalıyor.

`çağıran` sınıfında sarkan durum, bu dersin iki örneğinde de görülen biçimdir: belleği
ayıran ve `free` çağırma yükümlülüğü taşıyan taraf, bırakma sonrası `NULL` atamayı
unutursa (ya da `kopya_isaretci.c`'deki gibi ikinci bir kopya taşıyorsa), o işaretçi
sarkan kalır.

`çağrılan` sınıfında ise sarkan durum ters yönden gelir: bir işlev kendi tuttuğu bir
bloğu (`sahip.c` gibi bir çekirdeğin sahiplendiği veriyi düşün) kendi içinde
`free` ile bırakırsa, ama çağırana bu bloğa daha önce verdiği bir işaretçiyi geri
çekmezse, çağıranın elindeki kopya artık sarkan bir işaretçidir — çağıran bunu bilmez,
çünkü çağrılan bunu ona hiçbir şekilde bildirmedi. Bu yüzden `çağrılan` sınıfındaki
işlevlerin sözleşmesi açık olmalı: bir bloğu ne zaman bıraktığını ve bu bırakmanın
çağırandaki kopyaları geçersiz kıldığını belgelemek çağrılan tarafın sorumluluğudur.

Üçü de aynı gözleme varıyor: sarkan işaretçi, bir işaretçinin **taşıdığı adresin**
hâlâ geçerli bir bloğu gösterip göstermediğiyle ilgili bir sorudur ve bu soru hangi
sınıftan geldiğine bakılmaksızın dilin kendisi tarafından yanıtlanmaz — yanıt her
zaman programcının izlemesine kalır. Üç kaynak arasındaki fark, sorunun **ne zaman**
fark edilebilir olduğudur: `dil` sınıfındaki hata genellikle işlev döner dönmez, ilk
kullanımda ortaya çıkar; `çağıran` sınıfındaki hata bu dersteki gibi bir `NULL`
atamasının unutulmasıyla gecikebilir; `çağrılan` sınıfındaki hata ise en geç fark
edilenidir, çünkü çağıran tarafın kendi kodunda hiçbir şey değişmemiştir — yalnız
çağırdığı işlevin iç davranışı değişmiştir.

#### Rust'ın Ödünç Denetleyicisiyle Farkı

C'de bırakılmış belleği kimin gözettiği sorusu, Sahiplik ve Bellek Modeli kursunda (M10/K02) ele alınan Rust'ın
ödünç denetleyicisinden farklı bir yerde duruyor: Rust'ta bir değerin ömrü derleme zamanında
belirlenir ve ömrü biten bir referans derleyici tarafından reddedilir, C'de ise `p = NULL`
gibi bir alışkanlık dışında hiçbir mekanizma bir işaretçinin hâlâ geçerli bir bloğu
gösterip göstermediğini denetlemez. Rust'ta yukarıdaki `kopya_isaretci.c` örneğinin bir
benzeri **derlenmezdi** — `q`'ya `p`'nin ödünç verildiği an, ödünç denetleyici `p`'nin
serbest bırakılmasını `q` hâlâ yaşarken engellerdi. C'de bu denetim yok; `q`'nun sarkan
olduğunu bilmek tamamen programcının derste burada yapılan türden bir izlemesine kalır.

#### Sarkan İşaretçiyi Derleyici Değil, Araç Yakalar

Bu derste kullanılan tüm denetimler (`== NULL`, `!= NULL`) elle yazılan, programcının
kendi eklediği kontrollerdir — derleyicinin kendisi bir işaretçinin sarkan olup
olmadığını bilmez ve bunu asla otomatik olarak işaretlemez. Gerçek projelerde bu
boşluk çalışma zamanı araçlarıyla kapatılır: dinamik çözümleyiciler bir programı çalıştırırken her `malloc`/`free` çiftini izler
ve bir blok bırakıldıktan sonra o bloğa erişilirse (dolaylama, çift bırakma) bunu
raporlar. Bu araçlar derleme zamanında değil, **çalışma zamanında** ve yalnız o
koşumda gerçekten tetiklenen yollar için çalışır — Rust'ın derleme zamanı ödünç
denetleyicisinin tersine, bir sarkan işaretçi hatası yakalanmadan önce en az bir kez
gerçekten koşmuş olmalıdır. Bu, C'deki savunmanın doğasını özetliyor: dil kendisi
hiçbir garanti vermiyor, ama disiplin (bu derste görülen `NULL` atama alışkanlığı) ve
araçlar (çalışma zamanı denetleyicileri) birlikte bu garantinin eksikliğini
telafi ediyor.

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

`p = NULL` ataması, sarkan işaretçi sorununu **ortadan kaldırmıyor** — yalnız o tek
işaretçi için görünür kılıyor. Yukarıdaki `kopya_isaretci.c` bunu somut olarak
gösterdi: `q`, `p`'yle aynı adresi taşımasına rağmen `p = NULL` satırından hiç
etkilenmedi. Bu ders tek işaretçili ve iki işaretçili durumu ayrı ayrı ölçtü; üç ya da
daha fazla işaretçinin aynı bloğu paylaştığı, ya da bir işaretçinin bir yapı alanında
saklandığı daha karmaşık paylaşım örüntüleri, kursun Dinamik Bellek konusundaki Serbest
Bırakma Disiplini dersinin konusu. Oradaki temel soru aynı kalıyor: bir bloğu bırakma
yetkisine kim sahip ve bırakıldığını diğer kopyalara kim bildirecek.

#### Özet

- Boş işaretçi (`NULL`) **denetlenebilir bir değerdir**; `dolaylamadan önce == NULL ile
  sınanabilir ve bu ders bunu her ayırma çağrısından sonra uyguladı.
- Sarkan işaretçi, bırakılmış bir bloğu göstermeye **devam eden** bir işaretçidir; bu
  ders onu hiç dolaylamadı ya da tekrar bırakmadı, yalnız betimledi.
- `free` çağrısından hemen sonra işaretçiye `NULL` atamak, sarkan durumu denetlenebilir
  bir boş duruma **dönüştürüyor**.
- Boş ve sarkan işaretçi, çekirdeğin **sahipsiz** sınıfının iki farklı yüzüdür: biri
  bırakılmamış bir sızıntı, diğeri bırakılmış ama hâlâ gösterilen bir blok. `p = NULL`
  yalnız **o tek işaretçi** için geçerlidir; `kopya_isaretci.c`'nin gösterdiği gibi
  aynı bloğu gösteren başka bir kopya varsa o kopya hâlâ sarkandır.
- Sarkan durum yalnız `çağıran`'ın unuttuğu bir `NULL` atamasından değil, `dil`
  sınıfında bir yerel değişkenin adresinin döndürülmesinden ya da `çağrılan`'ın kendi
  bıraktığı bir bloğu çağırana bildirmemesinden de doğabilir — üçü de aynı soruya
  (adres hâlâ geçerli mi) farklı bir yoldan varır.
- Çift bırakma (`free`'yi aynı bloğa iki kez çağırmak) da sarkan işaretçinin bir
  sonucudur; `free(NULL)`'ın standart tarafından zararsız tanımlanması, `p = NULL`
  alışkanlığını bu ikinci tehlikeye karşı da koruyucu kılar — `guvenli_ikinci_cagri.c`
  bunu somut olarak gösterdi.

#### Sonraki Adım

Bu derse kadarki dört ders, işaretçilerin `int *`, `double *` gibi **somut tiplerini**
kullandı — tip her zaman gösterilen değerin ne olduğunu söylüyordu. Bir sonraki ders,
`void` ve Opak İşaretçiler, bir işaretçinin tipinin bilinçli olarak **gizlendiği**
durumları ölçüyor: çekirdekteki `opak kayit` gözlemi, `Kayit` yapısının içeriğini
tamamen saklarken sahipliği nasıl `çağrılan` sınıfına taşıdığını gösteriyor. Bu
derste görülen sarkan işaretçi tehlikesi orada da geçerli kalıyor — bir opak kaydın
adresini elinde tutan çağıran, o kaydı bırakma yükümlülüğü `çağrılan`'a ait olduğu
için, kaydın ne zaman geçersiz olduğunu ancak `çağrılan`'ın sözleşmesinden öğrenebilir;
kaydın içeriği gizli olduğu için bu sözleşme, tip tanımının kendisinden değil, yalnız
dokümantasyondan gelir.

### `void` ve Opak İşaretçiler

Source: https://academia.sh/tr/kurslar/c-bellek/void-ve-opak-isaretciler

Bir void işaretçinin gösterdiği tipin bilinçli olarak gizlenmesi ve opak bir tipin içeriğini tamamen saklayarak sahipliği çağrılan modülde tutması.

Buraya kadarki dört ders, bir işaretçinin gösterdiği değerin tipini hep açıkça biliyordu:
`int *`, `double *`. Bu ders, tipin bilinçli olarak **gizlendiği** iki durumu ölçüyor:
`void *` — herhangi bir nesne tipini gösterebilen ama kendisi dolaylanamayan bir işaretçi
— ve **opak tip** — içeriği tanımlandığı birimin dışına hiç sızmayan bir yapı. İkisi de
çekirdekteki `sahip.h`/`sahip.c` biriminin temelini oluşturuyor; sınıf burada **çağrılan**:
belleği tutan modül, çağırana yalnız ödünç veriyor.

#### `void *`: Tipi Taşıyan Ama Dolaylanamayan İşaretçi

```c
// void_isaretci.c — void isaretci herhangi bir tipi tutar ama once somut tipe donusmelidir
#include <stdio.h>

static void yazdir_int(const void *veri) {
    const int *p = veri;
    printf("%s %d\n", "int olarak okunan deger", *p);
}

static void yazdir_double(const void *veri) {
    const double *p = veri;
    printf("%s %.1f\n", "double olarak okunan deger", *p);
}

int main(void) {
    int sayi = 17;
    double kesirli = 3.5;

    void *genel = &sayi;
    printf("%s %d\n", "genel isaretci sayinin adresine esit mi", (int)(genel == &sayi));

    yazdir_int(genel);
    genel = &kesirli;
    yazdir_double(genel);
    return 0;
}
```

```
genel isaretci sayinin adresine esit mi 1
int olarak okunan deger 17
double olarak okunan deger 3.5
```

**IS21.** `genel` bir `void *` değişkeni ve önce `&sayi`yi, sonra `&kesirli`yi
gösterebiliyor — aynı değişken, iki farklı tipteki nesnenin adresini taşıyabiliyor.
`genel == &sayi` karşılaştırması **1** veriyor; bu, `void *`'ın da diğer işaretçiler gibi
eşitlikle ölçülebildiğini gösteriyor. Ama `genel`'in kendisi hiçbir yerde `*genel` olarak
dolaylanmıyor — `yazdir_int` ve `yazdir_double` işlevleri önce onu kendi somut tiplerine
(`const int *`, `const double *`) atıyor, ancak ondan sonra dolaylıyorlar. `void *`'ın
kuralı budur: herhangi bir nesne işaretçisini **tutabilir**, ama gösterdiği değeri okumak
için önce bir tipe **dönüştürülmesi** gerekir.

#### `void *` Tipi Taşımaz: Dışarıdan Bir Etiket Gerekir

Önceki bölümdeki `genel` değişkeni her seferinde hangi tipi gösterdiğini **kod okuyan
insana** açıkça belliydi — önce `&sayi`, sonra `&kesirli`. Ama çalışma zamanında `void *`
kendisiyle birlikte hiçbir tip bilgisi taşımaz; bir `void *` değişkenine bakarak onun
`int *` mi `double *` mi olduğunu söylemenin bir yolu yok. Programlar bu boşluğu genellikle
**ayrı bir etiket** ile kapatır.

```c
// etiketli_deger.c — void bir isaretci tip tasimadigi icin ayri bir etiket eslik eder
#include <stdio.h>

typedef enum { TIP_INT, TIP_DOUBLE } Tip;

typedef struct {
    Tip tip;
    void *veri;
} EtiketliDeger;

static void yazdir(const EtiketliDeger *e) {
    if (e->tip == TIP_INT) {
        const int *p = e->veri;
        printf("%s %d\n", "int etiketli deger", *p);
    } else {
        const double *p = e->veri;
        printf("%s %.1f\n", "double etiketli deger", *p);
    }
}

int main(void) {
    int sayi = 42;
    double kesirli = 2.5;

    EtiketliDeger degerler[2] = {
        { TIP_INT, &sayi },
        { TIP_DOUBLE, &kesirli }
    };

    for (int i = 0; i < 2; i++) {
        yazdir(&degerler[i]);
    }
    return 0;
}
```

```
int etiketli deger 42
double etiketli deger 2.5
```

**IS22.** `EtiketliDeger` yapısı `void *veri`'nin yanına bir de `Tip tip` alanı
taşıyor; `yazdir` işlevi doğru dönüşümü **`tip` alanına bakarak** seçiyor, `veri`'nin
kendisine bakarak değil — çünkü `veri`'nin kendisi bunu söyleyemez. `degerler` dizisinin
her elemanı farklı bir tipe işaret ediyor ama aynı `EtiketliDeger` tipinde tutulabiliyor;
bu, `void *`'ın neden çoğu zaman **yalnız başına değil, bir etiketle birlikte** kullanıldığını
gösteriyor. Etiket yanlış olsaydı (örneğin `TIP_DOUBLE` işaretlenmiş ama aslında `int *`
gösteren bir giriş), `yazdir` yanlış tipe dönüştürüp yanlış bir değer okurdu — bu tür bir
hata derleyici tarafından yakalanmaz, çünkü `void *` her iki durumda da aynı şekilde
derlenir.

Bu deseni C standart kütüphanesinde de görürüz: `qsort` ve `bsearch` karşılaştırma
işlevlerine `const void *` çiftleri geçirir, ama hangi tipi karşılaştırdığını hiçbir yerde
söylemez — bu bilgi çağıranın kafasında, karşılaştırma işlevinin kendi gövdesinde saklıdır.
`EtiketliDeger` bu örtük sözleşmeyi **açık bir alana** taşıyor: tip bilgisi artık işlevin
gövdesinde gömülü bir varsayım değil, verinin yanında duran, okunabilen bir değer. İki
yaklaşım da aynı riski taşıyor — etiket ya da varsayım yanlışsa sonuç tanımsız — ama
`EtiketliDeger` bu riski en azından **görünür** kılıyor, `sizeof`'un ya da parametre
sırasının kendiliğinden doğru olacağını varsaymak yerine.

#### Opak Tip: İçeriği Hiç Sızmayan Yapı

Bir önceki bölümde `void *` tipi gizledi ama boyutu gizlemedi — çağıran taraf hangi tipe
dönüştüreceğini biliyordu. Opak bir tip bir adım öteye gidiyor: yapının **alanlarını**
bile çağırana göstermiyor. Bu ders, çekirdeğin `sahip.h`/`sahip.c` birimini bu dersin
kendi dosyaları içinde yeniden kurarak bunu ölçüyor.

```c
// 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_birak(void *blok);
int ayrilan_sayisi(void);
int birakilan_sayisi(void);

#endif
```

```c
// 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_birak(void *blok) {
    if (blok != NULL) {
        birakilan++;
    }
    free(blok);
}

int ayrilan_sayisi(void) {
    return ayrilan;
}

int birakilan_sayisi(void) {
    return birakilan;
}
```

```c
// sahip.h — kaydin ici gizli, sahiplik sozlesmesi yorumlarda yazili
#ifndef SAHIP_H
#define SAHIP_H

#include <stddef.h>

typedef struct Kayit Kayit;

/* Cagiran sahiplenir ve kayit_yok_et ile birakir. Ad sinira sigmazsa NULL
   doner; o ana kadar ayrilan bellegi islev kendisi birakir. */
Kayit *kayit_olustur(const char *ad, size_t sinir);

/* Kaydi ve kaydin icindeki ad tamponunu birlikte birakir. */
void kayit_yok_et(Kayit *k);

/* Kayit sahiplenir: donen isaretci kayit yok edilene dek gecerlidir,
   cagiran onu birakmaz. */
const char *kayit_ad(const Kayit *k);

/* Cagiran sahiplenir: donen kopya say_birak ile birakilir. */
char *kayit_ad_kopyasi(const Kayit *k);

#endif
```

```c
// sahip.c — sozlesmenin uygulandigi birim
#include <string.h>
#include "sahip.h"
#include "sayac.h"

struct Kayit {
    char *ad;
    size_t uzunluk;
};

Kayit *kayit_olustur(const char *ad, size_t sinir) {
    Kayit *k = say_ayir(sizeof *k);
    if (k == NULL) {
        return NULL;
    }
    k->uzunluk = strlen(ad);
    if (k->uzunluk >= sinir) {
        say_birak(k);
        return NULL;
    }
    k->ad = say_ayir(k->uzunluk + 1);
    if (k->ad == NULL) {
        say_birak(k);
        return NULL;
    }
    memcpy(k->ad, ad, k->uzunluk + 1);
    return k;
}

void kayit_yok_et(Kayit *k) {
    if (k == NULL) {
        return;
    }
    say_birak(k->ad);
    say_birak(k);
}

const char *kayit_ad(const Kayit *k) {
    return k->ad;
}

char *kayit_ad_kopyasi(const Kayit *k) {
    char *kopya = say_ayir(k->uzunluk + 1);
    if (kopya != NULL) {
        memcpy(kopya, k->ad, k->uzunluk + 1);
    }
    return kopya;
}
```

```c
// opak_kayit.c — Kayit yapisinin alanlari yalniz bu birimin disinda tanimli
#include <stdio.h>
#include "sahip.h"
#include "sayac.h"

int main(void) {
    Kayit *k = kayit_olustur("envanter", 32);
    if (k == NULL) {
        printf("%s\n", "olusturma basarisiz");
        return 1;
    }

    printf("%s %s\n", "kayit adi", kayit_ad(k));

    char *kopya = kayit_ad_kopyasi(k);
    printf("%s %s\n", "kopyalanan ad", kopya != NULL ? kopya : "yok");
    say_birak(kopya);

    kayit_yok_et(k);
    printf("%s %d\n", "birakma sonrasi sayac farki", ayrilan_sayisi() - birakilan_sayisi());
    return 0;
}
```

```
kayit adi envanter
kopyalanan ad envanter
birakma sonrasi sayac farki 0
```

**IS23.** `opak_kayit.c` dosyası `struct Kayit`'in **hiçbir alanına doğrudan erişmiyor** —
`sahip.h` yalnız `Kayit *` tipini ve dört işlevi bildiriyor, yapının içeriği `sahip.c`
biriminde kalıyor. `kayit_ad(k)` çağrısı yapının içindeki adı **ödünç** veriyor —
`sahiplik.c`'deki `odunc verilen ad` satırı bu çağrıyı kullanmıştı ve sayaç farkı **0**
çıkmıştı, çünkü ödünç bir bellek ayırmıyor. `kayit_yok_et(k)` çağrısından sonra
`ayrilan_sayisi() - birakilan_sayisi()` **0** veriyor — kaydın hem kendisi hem içindeki ad
tamponu tam olarak bırakıldı. Sahiplik burada **çağrılan** sınıfına ait: `Kayit`'i tutan
birim (`sahip.c`), onu oluşturan da yok eden de kendisi, çağıran taraf yalnız `kayit_ad`
ile bir **ödünç** alıyor ve bu ödüncü bırakmıyor.

#### Aynı Sözleşme Birden Çok Örnekte

`sahip.h`'daki sözleşme tek bir `Kayit` örneği için değil, **her örnek için aynı**
şekilde geçerli olmalı — hem başarılı oluşturmalarda hem `kayit_olustur`'ün `NULL`
döndürdüğü başarısız çağrıda.

```c
// coklu_kayit.c — ayni opak sozlesme birden fazla Kayit orneginde de gecerli kalir
#include <stdio.h>
#include "sahip.h"
#include "sayac.h"

int main(void) {
    Kayit *birinci = kayit_olustur("depo", 32);
    Kayit *ikinci = kayit_olustur("sevkiyat", 32);
    Kayit *basarisiz = kayit_olustur("cokuzunbiradisimlimitiasandeger", 8);

    printf("%s %d\n", "basarisiz olusturma bos mu", (int)(basarisiz == NULL));

    if (birinci != NULL && ikinci != NULL) {
        printf("%s %s\n", "birinci kayit adi", kayit_ad(birinci));
        printf("%s %s\n", "ikinci kayit adi", kayit_ad(ikinci));
    }

    kayit_yok_et(birinci);
    kayit_yok_et(ikinci);
    kayit_yok_et(basarisiz);
    printf("%s %d\n", "tum birakmalardan sonra sayac farki",
           ayrilan_sayisi() - birakilan_sayisi());
    return 0;
}
```

```
basarisiz olusturma bos mu 1
birinci kayit adi depo
ikinci kayit adi sevkiyat
tum birakmalardan sonra sayac farki 0
```

**IS24.** `basarisiz`, `sinir` (`8`) verilen addan (`cokuzunbiradisimlimitiasandeger`)
küçük olduğu için `NULL` dönüyor — `basarisiz olusturma bos mu` **1**. `birinci` ve
`ikinci` bağımsız birer `Kayit` örneği; her biri kendi `ad` tamponunu ayırıyor ve
`kayit_ad` çağrıları birbirini etkilemiyor. `kayit_yok_et(basarisiz)` çağrısı `NULL`
üzerinde çalışıyor — `sahip.c`'deki `kayit_yok_et` bunu `k == NULL` denetimiyle zaten
karşılıyor, bu yüzden burada ayrı bir denetim yazmaya gerek yok. Üç çağrının (ikisi
başarılı, biri başarısız) hepsinden sonra `ayrilan_sayisi() - birakilan_sayisi()`
**0** veriyor — başarısız çağrı kendi ayırdığı `k` bloğunu `kayit_olustur`'ün içinde
zaten bırakmıştı, bu yüzden dışarıdan gelen `kayit_yok_et(NULL)` çağrısının hiçbir şey
yapmaması sayaçları bozmuyor. Bu, `çağrılan` sınıfının bir başka yüzü: sözleşme yalnız
başarı yolunda değil, **başarısızlık yolunda da** kendi bıraktığı belleği kendisi
temizliyor.

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

`void *` tip bilgisini **saklıyor** ama gizlemiyor — çağıran taraf hangi somut tipe
dönüştüreceğini bilmek zorunda, aksi hâlde dönüştürme yanlış olur ve sonuç tanımsızdır.
Opak tip ise bunun ötesine geçiyor: çağıran taraf `Kayit`'in kaç alanı olduğunu, alanların
adlarını ya da boyutlarını **hiç bilmiyor** — yalnız `sahip.h`'ın bildirdiği işlevlerle
etkileşiyor. Bu, bir esneklik-güvenlik değişimidir: opak bir tip, iç yapısını değiştirmeyi
(yeni bir alan eklemeyi) dış arayüzü bozmadan mümkün kılar, ama her erişim bir işlev
çağrısı gerektirir — doğrudan alan erişimi yoktur.

Bu güvence, yalnız `void *` kullanan taraf disiplinli olduğu sürece geçerli. `etiketli_deger.c`'deki
`yazdir`, `e->tip` alanına bakıp doğru dönüştürmeyi seçiyor; ama derleyici bu eşleşmeyi
**denetlemiyor** — `Tip` alanını hiç okumadan bir `TIP_DOUBLE` değerini `const int *`'a
dönüştürüp dolaylasaydık, bellekteki bit örüntüsü `double` olarak yazılmışken `int` olarak
okunacaktı. Bu blok bilerek koşturulmuyor: sonucu tanımsızdır ve platforma, derleyiciye,
hatta optimizasyon düzeyine göre değişken. Kod hiçbir zaman çökmeyebilir de, sessizce yanlış
bir sayı basabilir de — tanımsız davranışın asıl tehlikesi budur, hatanın görünür bir sinyal vermeden
geçmesi. `EtiketliDeger`'in `tip` alanı bu riski ortadan kaldırmıyor, yalnızca **görünür**
kılıyor: yanlış dönüştürme hâlâ mümkün, ama artık `if (e->tip == TIP_INT)` gibi bir denetimle
önlenebilir bir hataya dönüşüyor. Ham bir `void *` parametre alan bir işlevde bu denetim
imkânsızdır, çünkü tip bilgisi hiçbir yerde tutulmuyor; `etiketli_deger.c` tipi işaretçinin
yanında taşıyarak bu boşluğu dolduruyor.

Opak tipte aynı risk farklı bir yerde duruyor. `coklu_kayit.c`'deki `birinci` ve `ikinci`
aynı `Kayit *` tipini taşısa da, ikisinin `veri`/`ad` alanları `sahip.c`'nin dosyasında
gizli — çağıran taraf `birinci->ad` yazmaya **çalışamaz bile**, çünkü `Kayit` yapısının
tanımı `sahip.h`'da yok, yalnız `sahip.c` içinde. Bu, `void *`'ın
tip-güvenliği sorununu tamamen farklı bir yoldan çözüyor: yanlış dönüştürme riskini
kaldırmak yerine, dönüştürmeye giden yolu **derleme zamanında kapatıyor**. İki yaklaşımın
ortak noktası, ikisinin de veriyi kim sahipleniyor sorusuna aynı yanıtı vermesi: `sahip.c`
içindeki `kayit_olustur` ve `kayit_yok_et`, `birinci` ve `ikinci`'nin ömrünü baştan
sona kendisi yönetiyor — çağıran taraf yalnız bir tutamak (`Kayit *`) alıyor, içeriğine asla
dokunmuyor. Bu yüzden dersin sahiplik sınıfı **çağrılan**: `sahip.c` hem ayırıyor hem
serbest bırakıyor, çağıran tarafın tek görevi elindeki tutamağı doğru işlevlere geçirmek.

Ama bu görev de bir yükümlülüktür ve opaklık onu gizlemez. Opak tip yapının **içini** gizler,
**sahipliği** gizlemez: `kayit_olustur`'un döndürdüğü tutamağı `kayit_yok_et`'e geri vermek
yine çağıranın işidir ve bu yükümlülük imzada değil, yalnız `sahip.h`'daki yorumda yazılıdır.
Çağıran bu çağrıyı unutursa derleyici hiçbir şey söylemez; iki blok da sahipsiz kalır.

#### Özet

- `void *` herhangi bir nesne işaretçisini **tutabilir** ama kendisi dolaylanamaz; önce
  somut bir tipe (`int *`, `const double *` gibi) dönüştürülmesi gerekir.
- Bir `void *` değişkeni zaman içinde farklı tipteki nesnelerin adresini taşıyabilir;
  eşitlik karşılaştırması diğer işaretçiler gibi çalışır. Ama tipi kendisi taşımadığı için
  `EtiketliDeger` örneğindeki gibi ayrı bir etiket olmadan hangi tipe dönüştürüleceği
  derleyici tarafından denetlenmez — yanlış dönüştürme tanımsız davranıştır ve koşturulmadan, yalnız
  anlatılarak gösterilir.
- Opak bir tip (`Kayit`) alanlarını tanımlandığı birimin dışına **hiç sızdırmaz**;
  çağıran yalnız bildirilen işlevlerle etkileşir. `coklu_kayit.c` bu sözleşmenin aynı
  program içinde birden çok bağımsız örnekte de geçerli kaldığını gösteriyor.
- Opak bir tipi tutan birim onu hem oluşturur hem yok eder — sınıf **çağrılan**; çağıran
  tarafa yalnız ödünç verilen değerler (`kayit_ad` gibi) bırakılmaz. Bu, başarısızlık
  yolunda da geçerli: `kayit_olustur` başarısız olduğunda kendi ayırdığı bloğu kendisi
  bırakıyor, çağıran tarafın `kayit_yok_et(NULL)` çağrısı yalnız güvenli bir hiçlik.
- Bir ödünç (`const char *` dönen `kayit_ad`) ile bir kopya (`char *` dönen
  `kayit_ad_kopyasi`) farklı sahiplik taşır — biri bırakılmaz, diğeri bırakılmalıdır.

#### Sonraki Adım

İşaretçiler konusunun son dersi, Fonksiyon İşaretçileri, veri işaretçilerinden ayrı bir türü ölçüyor:
bir işlevin kendisini gösteren ve başka bir işleve parametre olarak geçirilebilen
işaretçiler. Bu derste `void *`'ın tip bilgisini gizlemeden taşıması ile opak tipin onu
tamamen gizlemesi arasındaki fark ölçüldü; fonksiyon işaretçilerinde de benzer bir gerilim
var — bir geri çağrının imzası hangi bağlamı (`context`) alacağını söyler, ama bağlamın
içeriğini ya da sahipliğini garanti etmez. Bir geri çağrının hangi bağlamı taşıdığı ve bu
bağlamın sahipliğinin kime ait olduğu, bu topluluğun son sorusu.

### Fonksiyon İşaretçileri

Source: https://academia.sh/tr/kurslar/c-bellek/fonksiyon-isaretcileri

Bir işlevin kendisini gösteren ve başka bir işleve parametre olarak geçirilebilen işaretçiler; bir geri çağrının taşıdığı bağlamın sahipliğinin çağırana ait olduğu.

Şimdiye kadarki beş ders, bir işaretçinin **veriyi** — bir tamsayıyı, bir diziyi, opak bir
kaydı — gösterdiği durumları ölçtü. Bu son ders farklı bir şeyi gösteren bir işaretçiyi
ölçüyor: **işlevin kendisini**. Bir fonksiyon işaretçisi, derlenmiş bir işlevin adresini
tutar ve o işlevi dolaylı biçimde çağırmayı sağlar — bu, bir işlevi çalışma zamanında
seçmenin ya da başka bir işleve **parametre olarak geçirmenin** temelidir.

Bir fonksiyon işaretçisinin bildirimi ilk bakışta karışık görünür: `int (*islem)(int, int)`
ifadesindeki parantezler rastgele değil, zorunlu — `int *islem(int, int)` yazılsaydı bu,
`int *` döndüren ve `islem` adlı bir işlevin **bildirimi** olurdu, `islem` adında bir
işaretçi değil. Parantezler `*islem`'i önce bir isim olarak gruplandırıp derleyiciye
"`islem` bir işaretçidir, gösterdiği şey şu imzaya uyan bir işlevdir" dedirtiyor. Bu
karmaşıklık genellikle bir `typedef` ile gizlenir — nitekim `geri_baglam.c`'deki
`void (*islev)(int, void *)` parametre tipi, `islem_tablosu.c`'deki `IkiliIslem` tanımıyla
aynı deseni izliyor, yalnız birinde tip doğrudan yazılmış, diğerinde bir isme bağlanmış.

#### Fonksiyon İşaretçisini Tanımlamak ve Çağırmak

```c
// islev_isaretcisi.c — bir islev isaretcisi, derlenmis bir islevin adresini tutar
#include <stdio.h>

static int topla(int a, int b) {
    return a + b;
}

static int carp(int a, int b) {
    return a * b;
}

int main(void) {
    int (*islem)(int, int) = topla;
    printf("%s %d\n", "topla uzerinden dolayli cagri", islem(3, 4));

    islem = carp;
    printf("%s %d\n", "carp uzerinden dolayli cagri", islem(3, 4));

    printf("%s %d\n", "islem toplaya esit mi", (int)(islem == topla));
    printf("%s %d\n", "islem carpa esit mi", (int)(islem == carp));
    return 0;
}
```

```
topla uzerinden dolayli cagri 7
carp uzerinden dolayli cagri 12
islem toplaya esit mi 0
islem carpa esit mi 1
```

**IS25.** `int (*islem)(int, int)` bildirimi, `islem`'in iki `int` alıp bir `int`
döndüren bir işlevin adresini tutacağını söylüyor. `islem = topla` atamasından sonra
`islem(3, 4)` çağrısı **7** veriyor — `topla`'yı dolaylı biçimde çalıştırıyor. `islem`
daha sonra `carp`'a **yeniden atanıyor** ve aynı çağrı yazımıyla (`islem(3, 4)`) bu kez
**12** veriyor. `islem == topla` **0**, `islem == carp` **1** — bu, fonksiyon
işaretçilerinin de veri işaretçileri gibi eşitlikle karşılaştırılabildiğini gösteriyor;
`islem` şu an hangi işlevi gösteriyorsa onunla eşleşiyor.

#### Geri Çağrı: Bir İşlevi Başka Bir İşleve Geçirmek

```c
// geri_cagri.c — bir islevi parametre olarak gecirmek, calisma zamaninda davranisi secer
#include <stdio.h>

static int kare(int x) {
    return x * x;
}

static int negatif(int x) {
    return -x;
}

static void uygula_ve_yazdir(int deger, int (*donusum)(int), const char *etiket) {
    printf("%s %d\n", etiket, donusum(deger));
}

int main(void) {
    uygula_ve_yazdir(6, kare, "kare donusumu");
    uygula_ve_yazdir(6, negatif, "negatif donusumu");
    return 0;
}
```

```
kare donusumu 36
negatif donusumu -6
```

**IS26.** `uygula_ve_yazdir` işlevi, `int (*donusum)(int)` parametresiyle **hangi
işlevin çalıştırılacağını çağıranın belirlemesine** izin veriyor — işlevin kendi içinde
`kare` ya da `negatif` adı hiç geçmiyor. İlk çağrıda `kare` geçiyor ve sonuç **36**
(6*6), ikinci çağrıda `negatif` geçiyor ve sonuç **-6** çıkıyor. Bu düzen, aynı
`uygula_ve_yazdir` gövdesini değiştirmeden farklı davranışlar elde etmeyi sağlıyor —
davranış, derleme zamanında değil çağrı anında **parametre olarak** seçiliyor. Bu
düzene **geri çağrı** deniyor: `donusum`, `uygula_ve_yazdir`'in içinden geriye, çağıranın
sağladığı işleve çağrı yapıyor.

Bu düzenin `topla`/`carp` örneğinden farkı, işlevin **kendisinin** artık bir değişkende
tutulmaması, doğrudan bir parametre olarak akmasıdır. `islem` değişkeni `main` içinde
kalıcıydı ve yeniden atanabiliyordu; `donusum` ise yalnız `uygula_ve_yazdir`'in çalıştığı
süre boyunca var olan geçici bir parametre. İki kullanım da aynı fonksiyon işaretçisi
mekanizmasını paylaşıyor, ama biri **durum tutmayı** (`islem` hangi işlevi gösteriyor),
diğeri **tek seferlik yönlendirmeyi** (bu çağrıda hangi işlev kullanılacak) örnekliyor.

#### Bağlamın Sahipliği: Geri Çağrıya Taşınan Veri

Bir geri çağrı çoğu zaman yalnız bir değeri değil, o değere eşlik eden bir **bağlamı**
(context) da taşır. Bu bağlam kimin sahipliğinde olmalı? Aşağıdaki örnek, çağıranın bir
bağlam sağladığı ve bu bağlamın ömrünü kendisinin yönettiği durumu ölçüyor.

```c
// geri_baglam.c — geri cagriya tasinan baglamin sahibi onu saglayan cagirandir
#include <stdio.h>

typedef struct {
    int carpan;
    int toplam;
} Baglam;

static void birikti(int deger, void *baglam) {
    Baglam *b = baglam;
    b->toplam += deger * b->carpan;
}

static void dizi_uzerinde_calistir(const int *dizi, int uzunluk,
                                    void (*islev)(int, void *), void *baglam) {
    for (int i = 0; i < uzunluk; i++) {
        islev(dizi[i], baglam);
    }
}

int main(void) {
    int degerler[4] = {1, 2, 3, 4};
    Baglam b = {.carpan = 3, .toplam = 0};

    dizi_uzerinde_calistir(degerler, 4, birikti, &b);
    printf("%s %d\n", "carpanli toplam", b.toplam);

    b.carpan = 1;
    b.toplam = 0;
    dizi_uzerinde_calistir(degerler, 4, birikti, &b);
    printf("%s %d\n", "carpansiz toplam", b.toplam);
    return 0;
}
```

```
carpanli toplam 30
carpansiz toplam 10
```

**IS27.** `Baglam` yapısı `main` içinde **yerel bir değişken** olarak tanımlanıyor —
`say_ayir` ya da `malloc` ile hiç ayrılmıyor, sınıfı **dil**. Ama `dizi_uzerinde_calistir`
işlevine `&b` olarak geçiyor ve o işlev bunu `birikti`'ye `void *baglam` parametresi
üzerinden **aktarıyor**. `birikti`, `baglam`'ı kendi sahiplenmiyor — yalnız `main`'in
sahip olduğu belleğe yazıyor. İlk koşumda çarpan **3** ve toplam **30** (3*(1+2+3+4)),
ikinci koşumda çarpan **1**'e sıfırlanıyor ve toplam **10** çıkıyor — `b` yeniden
kullanılabiliyor çünkü ömrü hâlâ `main`'e ait. Geri çağrıya geçen bir bağlam, dinamik
olarak ayrılmış olsaydı bile aynı kural geçerli olurdu: bağlamı **sağlayan taraf**
(çağıran) onu bırakmakla yükümlü kalırdı, `birikti` gibi bir geri çağrı işlevi onu asla
kendi sahiplenmez.

Bu düzen, bu kursun ilk dersindeki `opak kayit` gözlemiyle aynı sahiplik biçimini
paylaşıyor ama ters yönden: orada `çağrılan` (`sahip.c`) kendi verisini tutuyor ve
çağırana yalnız bir ödünç veriyordu, burada ise **çağıran** kendi verisini tutuyor ve
`çağrılan` (`birikti`, `dizi_uzerinde_calistir`) bu veriye yalnız bir işaretçi üzerinden
erişiyor. İki durumda da veri tek bir tarafta kalıyor; değişen yalnızca hangi tarafın
belleği tuttuğu. `void *baglam` parametresi burada `05-void-and-opaque-pointers.mdx`
dersindeki `void *`'la aynı kuralı izliyor: `birikti` işlevi `baglam`'ı önce `Baglam *`
tipine dönüştürüyor, ancak ondan sonra alanlarına erişiyor — tip bilgisi çağıran
tarafından taşınıyor, geri çağrı işlevi bunu yalnız geri kazanıyor.

#### Fonksiyon İşaretçisi Dizisi: Bir Dağıtım Tablosu

Bir fonksiyon işaretçisi tek başına yeniden atanabildiği gibi, bir **dizi** içinde de
tutulabilir — bu, çalışma zamanında hangi işlevin çalışacağını bir indeksle seçmeyi
sağlar.

```c
// islem_tablosu.c — bir islev isaretcisi dizisi, calisma zamaninda dagitim tablosu kurar
#include <stdio.h>

typedef int (*IkiliIslem)(int, int);

static int topla(int a, int b) { return a + b; }
static int cikar(int a, int b) { return a - b; }
static int carp(int a, int b) { return a * b; }

int main(void) {
    IkiliIslem tablo[3] = { topla, cikar, carp };
    const char *adlar[3] = { "topla", "cikar", "carp" };
    int sonuclar[3];

    for (int i = 0; i < 3; i++) {
        sonuclar[i] = tablo[i](10, 4);
        printf("%s %d\n", adlar[i], sonuclar[i]);
    }

    int toplam = 0;
    for (int i = 0; i < 3; i++) {
        toplam += sonuclar[i];
    }
    printf("%s %d\n", "sonuclarin toplami", toplam);
    return 0;
}
```

```
topla 14
cikar 6
carp 40
sonuclarin toplami 60
```

Bu tablo, `if`/`else if` zincirinin ya da bir `switch` ifadesinin yerini alan bir dağıtım
biçimidir: yeni bir işlem eklemek `tablo` ve `adlar` dizilerine bir eleman eklemekle
sınırlı kalır, çağıran kodun kendisi değişmez.

**IS28.** `tablo` dizisinin her elemanı farklı bir işlevin adresini tutuyor;
`tablo[i](10, 4)` çağrısı hangi işlevin koşacağını **çalışma zamanında, `i` indeksine
göre** seçiyor — kaynak kodunda `topla(10, 4)`, `cikar(10, 4)`, `carp(10, 4)` gibi üç
ayrı satır yazmaya gerek kalmıyor. `sonuclar` dizisi hem `tablo` hem de kendisi `main`
içinde yerel değişken olarak tanımlı; ikisi de sınıfı **çağıran** — `main` onları ayırıyor,
doldurup okuyor, işlev çağrısı bitince kapsam dışına çıkıp kendiliğinden geri veriyor. Bu,
`geri_baglam.c`'deki `Baglam b`'nin sahiplik biçimiyle aynı: dizi de yapı da `dil`
sınıfının otomatik ömrüyle yönetiliyor, ama dizinin *içeriği* (hangi işlevlerin hangi sırada
durduğu) tamamen `main`'in kararı — `topla`, `cikar` ve `carp` bu kararda hiç söz sahibi
değil, yalnız çağrıldıklarında kendi işlerini yapıyorlar.

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

Bir fonksiyon işaretçisinin imzası (`int (*)(int, int)` gibi) yalnız parametre ve dönüş
tiplerini eşleştirir — gösterdiği işlevin **ne yaptığını** ya da **yan etkilerinin**
neler olduğunu söylemez. Yukarıdaki `topla` ve `carp` aynı imzayı paylaşıyor ama tamamen
farklı işler yapıyor; bir geri çağrı parametresi alan bir işlev, kendisine geçirilen
işlevin davranışı hakkında derleme zamanında hiçbir garanti alamaz. Bu, işaretçi
tiplerinin sahiplik bilgisini taşımadığı önceki derslerdeki bulguyla aynı sınırın bir
başka biçimidir: tip, **şekli** garanti eder, **anlamı** garanti etmez.

Bu sınır, geri çağrıya taşınan bağlamla birleştiğinde ayrı bir riske dönüşür. `geri_baglam.c`'de
`dizi_uzerinde_calistir` çağrısı `birikti`'ye geçtiği anda `b` hâlâ `main`'in kapsamında,
dolayısıyla `baglam`'ın gösterdiği bellek geçerli. Ama bir geri çağrı **daha sonra**,
örneğin bir olay kuyruğuna kaydedilip çağıran işlev geri döndükten sonra çalışacaksa,
bağlam olarak geçirilen adres artık `dil` sınıfının otomatik ömrünü aşmış olabilir —
04. dersteki sarkan işaretçi durumunun aynısı, burada geri çağrı biçiminde. Bu blok bilerek
koşturulmuyor: bir işlev, yerel bir `Baglam` değişkeninin adresini bir geri çağrı kaydına
yazıp döner, sonra kayıtlı geri çağrı tetiklendiğinde `baglam`, artık kapsamı kapanmış bir
çağrı çerçevesini gösteriyor olurdu — dolaylamak tanımsız davranıştır. `dizi_uzerinde_calistir`
örneğinde bu risk yok, çünkü geri çağrı `main`'in kendi çağrı çerçevesi içindeyken, `b`
hâlâ yaşarken çalışıyor; risk yalnız bağlamın ömrü ile geri çağrının çalışma anı
**ayrıştığında** ortaya çıkıyor. Bu yüzden bağlam sağlayan tarafın (çağıran) yükümlülüğü
yalnız belleği bırakmakla sınırlı değil — geri çağrı ne zaman tetiklenebilirse bağlamın o
âna kadar yaşamasını da garanti etmek zorunda.

Bu desenin en yaygın kullanımı C standart kütüphanesinde: `qsort`, bir diziyi sıralamak
için bir `int (*)(const void *, const void *)` karşılaştırma işlevi ister, `bsearch` aynı
imzayı arama için kullanır. `qsort`'un kendisi hangi tipi sıraladığını **bilmiyor** —
`islem_tablosu.c`'deki `tablo[i]` çağrısıyla aynı dolaylama düzeni, ama bu kez dolaylanan
işlev çağıranın verdiği bir karşılaştırma kararı. `qsort`'a geçirilen karşılaştırma işlevi
de kendi parametrelerini önce doğru tipe dönüştürmek zorunda — tıpkı `birikti`'nin
`void *baglam`'ı önce `Baglam *`'a çevirmesi gibi; imzadaki `const void *` burada da tip
bilgisini taşımıyor, yalnız saklıyor. `dagitim tablosu` ve `qsort` deseni arasındaki fark
yalnız ölçek: biri üç sabit işlev arasında seçim yapıyor, diğeri çağıranın **her seferinde
farklı bir karşılaştırma mantığı** sağlamasına izin veriyor — ikisinin de temeli aynı: bir
işlevi veri gibi taşıyıp gerektiğinde dolaylı çağırmak.

#### Sahiplik Ekseni A Payı Boyunca: Dört Sınıf, Bir Sonuç

İşaretçiler konusunun altı dersi aynı soruyu altı farklı işaretçi biçiminde sordu: **bu belleği kim
sahipleniyor?** 01 ve 02. derslerde yanıt `dil`'di — `main` içindeki yerel değişkenler ve
diziler, kapsam kapanınca kendiliğinden geri veriliyordu, hiçbir taraf `free` çağırmıyordu.
03. derste sahiplik yine `dil`'de kaldı, ama sorun sahiplikten çok **bilgi kaybı**ydı: bir
dizi işaretçiye bozunduğunda uzunluğunu kaybediyordu, bu da `en_buyuk`, `matris_bozunmasi.c`
ve `ciktisi_da_donen.c`'nin üç farklı kanaldan aynı eksik bilgiyi tamamlamasını gerektiriyordu.
04. derste sahiplik üç kaynağa (dil, çağıran, çağrılan) ayrıldı ve ortak sonuç sarkan
işaretçiydi — bırakılmış bir bloğu göstermeye devam eden bir değer. 05. derste sahiplik ilk
kez **gizlenen** bir sınıfa taşındı: opak tip, `çağrılan`'ın kendi verisini tuttuğu ve
çağırana yalnız ödünç verdiği bir sözleşmeydi. Bu son derste sahiplik yeniden `çağıran`'a
döndü — ama bu kez veri değil, bir **işlev seçimi** ve ona eşlik eden bağlam söz konusu;
`geri_baglam.c`'deki `Baglam b` ve `islem_tablosu.c`'deki `tablo`/`sonuclar` dizileri,
`main`'in ayırdığı ve `main`'in geri aldığı belleklerdi.

Dört sınıfın (dil, çağıran, çağrılan, sahipsiz) ortak noktası, hiçbirinin işaretçinin
**tipinde** görünmemesiydi — `int *`, `Kayit *` ya da `int (*)(int, int)` hiçbiri hangi
tarafın bırakmakla yükümlü olduğunu söylemiyordu. Bu bilgi her zaman ya bir isimlendirme
kuralıyla (`_olustur`/`_yok_et` çiftleri), ya bir yorumla, ya da bir sözleşme belgesiyle
**dışarıdan** taşınmak zorundaydı. Sıradaki konu, Dinamik Bellek, bu dört sınıfı artık
`malloc`/`free` çiftinin kendisine uygulayacak; buradaki sınıflandırma değişmeyecek, yalnız
hangi işlevlerin hangi sınıfı temsil ettiği netleşecek.

#### Özet

- Bir fonksiyon işaretçisi (`int (*)(int, int)`) derlenmiş bir işlevin adresini tutar ve
  o işlevi dolaylı biçimde çağırmayı sağlar; farklı işlevlere yeniden atanabilir.
- Bir işlevi başka bir işleve parametre olarak geçirmek bir **geri çağrı** kurar;
  çağrılan işlevin davranışı, çalışma zamanında hangi işlevin geçirildiğine bağlı olarak
  değişir.
- Bir geri çağrıya taşınan bağlam, çoğunlukla `dil` sınıfında bir yerel değişkenin
  adresidir; geri çağrı işlevi bu bağlamı **sahiplenmez**, yalnız kullanır.
- Bağlamı sağlayan taraf **çağıran**dır ve bağlam dinamik olarak ayrılmış olsaydı da onu
  bırakma yükümlülüğü yine çağıranda kalırdı.
- Bir fonksiyon işaretçisinin imzası yalnız parametre ve dönüş tiplerini eşleştirir;
  gösterdiği işlevin davranışını garanti etmez.
- Bir fonksiyon işaretçisi dizisi (`islem_tablosu.c`'deki `tablo`) çalışma zamanında
  indeksle seçilen bir dağıtım tablosu kurar; `qsort`/`bsearch`'ün karşılaştırma
  parametresi aynı deseni standart kütüphanede kullanır.

#### Sonraki Adım

İşaretçiler konusu burada tamamlanıyor: dört sınıf (dil, çağıran, çağrılan, sahipsiz) altı ders
boyunca hem veri işaretçileri hem işlev işaretçileri üzerinden ölçüldü. Her derste
sınıfı belirleyen şey işaretçinin tipi değil, hangi tarafın ayırma ve bırakma işlemini
üstlendiğiydi — bu ayrım kaynak kodda hiçbir zaman doğrudan görünmedi, yalnız isimlendirme
ve sözleşmeyle iletildi. Sıradaki konu, Dinamik Bellek, bu dört sınıfı `malloc`, `calloc`,
`realloc` ve `free` işlevlerinin kendi davranışlarına — başarısız ayırma, yeniden
boyutlandırmanın taşıdığı riskler ve serbest bırakma disiplini — taşıyor.

## Dinamik Bellek

Ayırma, sıfırlayarak ayırma ve yeniden boyutlandırma işlevleri, serbest bırakma sözleşmesinin başlık dosyasında belgelenmesi, bellek sızıntısının sayaç farkıyla ölçülmesi, bir aracın yakalayıp yakalamadığı, ve belleğin otomatik, statik ve dinamik alanlara bölünmesi.

### Bellek Ayırma İşlevleri

Source: https://academia.sh/tr/kurslar/c-bellek/bellek-ayirma-islevleri

malloc, calloc, realloc ve free aynı öbekten çalışır ve dördü de aynı soruyu bırakır: ayırdığın bloğu kim bırakacak? Yanıt hep aynıdır — ayıran çağırandır; ders bunu sayaç farkıyla, sıfırlamayı doğrudan okuyarak, yeniden boyutlandırmayı ise eski sahipliğin değişmediğini göstererek ölçüyor.

Önceki altı ders bir işaretçinin gösterdiği belleğin nereden geldiğini hep önceden verilmiş
kabul etti: bir yerel değişkenin adresi, bir dizinin ilk elemanı, bir kaydın içindeki bir
alan. `Fonksiyon İşaretçileri` dersi bile bir geri çağrının bağlamını taşıyordu ama o
bağlamın kendisinin nasıl var olduğunu sormadı. Bu ders o boşluğu kapatıyor: bir blok, kimse
onu istemeden önce, önce **ayrılmak** zorundadır — ve ayıran taraf, dört işlevden hangisini
çağırırsa çağırsın, aynı yükü üstlenir.

C'nin standart kitaplığı dört ayırma ve bırakma işlevi verir: `malloc` istenen boyutta
sıfırlanmamış bir blok döndürür, `calloc` eleman sayısı ve eleman boyutunu ayrı parametre
alıp bloğu **sıfırlayarak** döndürür, `realloc` var olan bir bloğu büyütür ya da küçültür,
`free` bir bloğu bırakır. Dördü de aynı sözleşmeyi taşır: dönen işaretçinin **sahibi
çağırandır** — bloğu isteyen taraf, onu bırakmakla da yükümlüdür. Bu kursun ölçü ekseninde
bu, dört sınıftan en dolaysız olanıdır; burada bir sözleşme aramaya gerek yoktur, sözleşme
işlevin kendi imzasındadır.

#### Ayıran Bırakır

Ölçüm, kursun geri kalanında yeniden kullanılan aynı sayaçlı sarmalayıcıyla yapılıyor.
`sayac.c` her `say_ayir` ve `say_buyut` çağrısını, her `say_birak` çağrısını sayıyor; bir
gözlemin başındaki ve sonundaki fark **0** ise, o gözlemde ayrılan her şey bırakılmış demektir.

```c
// 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
```

```c
// 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;
}
```

Bu iki dosya kursun kalan on üç dersinde de aynı adla ve aynı içerikle dönüyor; burada ilk
kez göründükleri için tam hâlleriyle yazıldı. `say_ayir` `malloc`'ı, `say_buyut` `realloc`'ı
sarmalıyor — ikisi de yalnız sayacı artırıp gerçek işlevi çağırıyor, davranışı değiştirmiyor.

```c
// tahsis.c — malloc, calloc ve realloc'un cagiran sinifiyla olcumu
#include <stdio.h>
#include <stdlib.h>
#include "sayac.h"

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, sonra;

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

    /* --- malloc: ham blok, ayiran birakir --- */
    once = ayrilan_sayisi() - birakilan_sayisi();
    int *ham = say_ayir(3 * sizeof *ham);
    int toplam = 0;
    if (ham != NULL) {
        for (int i = 0; i < 3; i++) {
            ham[i] = i + 1;
            toplam += ham[i];
        }
    }
    say_birak(ham);
    sonra = ayrilan_sayisi() - birakilan_sayisi();
    snprintf(tampon, sizeof tampon, "toplam %d, sayac farki %d", toplam, sonra - once);
    gozle("ham blok", "cagiran", tampon);

    /* --- calloc: sifirlanmis blok, degerler dogrudan okunur --- */
    int *sifirlanan = calloc(4, sizeof *sifirlanan);
    int hepsi_sifir = 1;
    if (sifirlanan != NULL) {
        for (int i = 0; i < 4; i++) {
            if (sifirlanan[i] != 0) {
                hepsi_sifir = 0;
            }
        }
    }
    snprintf(tampon, sizeof tampon, "hepsi sifir %d", hepsi_sifir);
    gozle("sifirlanan blok", "cagiran", tampon);
    free(sifirlanan);

    /* --- realloc: yeniden boyutlandirma yeni bir sahip dogurmaz --- */
    once = ayrilan_sayisi() - birakilan_sayisi();
    int *buyuyen = say_buyut(NULL, 2 * sizeof *buyuyen);
    int korunan = 0;
    if (buyuyen != NULL) {
        buyuyen[0] = 7;
        int *yeni = say_buyut(buyuyen, 8 * sizeof *yeni);
        if (yeni != NULL) {
            buyuyen = yeni;
        }
        korunan = buyuyen[0];
    }
    say_birak(buyuyen);
    sonra = ayrilan_sayisi() - birakilan_sayisi();
    snprintf(tampon, sizeof tampon, "ilk eleman %d, sayac farki %d", korunan, sonra - once);
    gozle("yeniden boyutlandirma", "cagiran", tampon);

    printf("ayrilan %d birakilan %d fark %d\n", ayrilan_sayisi(), birakilan_sayisi(),
           ayrilan_sayisi() - birakilan_sayisi());

    return 0;
}
```

```
gozlem                        sahip     kanit
ham blok                      cagiran   toplam 6, sayac farki 0
sifirlanan blok               cagiran   hepsi sifir 1
yeniden boyutlandirma         cagiran   ilk eleman 7, sayac farki 0
ayrilan 2 birakilan 2 fark 0
```

**BE1.** Üç gözlemin üçü de sayaç farkını **0**'a döndürdü ve bunun tek nedeni var: her
ayırma çağrısının hemen ardından ya da işlev sonunda karşılığı olan bir bırakma çağrısı var.
Bu, dört işlevin ortak bir davranışı değil, **çağıranın verdiği bir söz**: `malloc`,
`calloc` ve `realloc` bir sözleşme dayatmaz, yalnız bir blok döndürür; bloğu bırakmayı
unutmak derleyicinin uyarısını da tetiklemez, bağlayıcının hatasını da. `ham blok` üç
elemanlık bir dizi gibi kullanıldı ve toplam **6** verdi — bu, önceki dersteki bozunmuş
dizilerden farklı olarak, tipi belli ve uzunluğu kaynakta sabit bir bloktu; farkı, bu
uzunluğun bu kez sabit bir tanımdan değil bir çalışma zamanı isteğinden gelmesiydi.

#### calloc'un Sıfırlaması

`malloc` istediği boyutu ayırır ama içeriğine dokunmaz: döndürdüğü bellek, önceden orada ne
duruyorsa onu taşır ve bu değer **tanımsızdır** — okunması güvenli değildir, bu yüzden ders
bu değeri hiçbir yerde basmıyor. `calloc` aynı isteği iki parametreyle alır — eleman sayısı
ve eleman boyutu — ve döndürdüğü bloğu **sıfırlar**. `sifirlanan blok` gözlemi bunu doğrudan
okuyarak kanıtlıyor: dört `int`'in dördü de, hiçbir atama yapılmadan, **sıfır** değerinde
bulundu.

Bu fark yalnız bir kolaylık değildir. `malloc` ile ayrılan bir blok, ilk kullanımdan önce
elle sıfırlanmazsa, üzerinde okunan her değer önceki bir ayırmanın artığı olabilir —
`calloc` bu riski işlevin kendi sözleşmesine taşır. İki elemanın çarpımı taştığında
(`eleman_sayisi * eleman_boyutu` bir tam sayı sınırını aşarsa) `calloc`'un bunu **kendisinin**
denetlemesi de aynı nedenle önemlidir: `malloc(eleman_sayisi * eleman_boyutu)` yazmak bu
denetimi çağırana bırakır, `calloc(eleman_sayisi, eleman_boyutu)` yazmak denetimi işlevin
içine alır. Sahiplik burada da değişmiyor — sıfırlanan blok da **çağıranın**dır ve `free`
ile bırakılması gerekiyor, ders bunu son satırda yapıyor.

#### Yeniden Boyutlandırma Yeni Bir Sahip Doğurmaz

`realloc` (burada `say_buyut` sarmalayıcısı üzerinden) dört işlevin en tuzaklısıdır, çünkü
tek çağrıda üç farklı şey olabilir: blok yerinde büyür, blok başka bir adrese taşınır ve
eski adres bırakılır, ya da istek karşılanamaz ve `NULL` döner. `yeniden boyutlandirma`
gözlemi ilk iki durumu tek akışta gösteriyor: `buyuyen` iki elemanlık ayrıldı, ilk elemanına
**7** yazıldı, sonra sekiz elemana büyütüldü. Çıktıdaki `ilk eleman 7` değeri, büyütme
sırasında adresin değişip değişmediğinden bağımsız olarak, **içeriğin taşındığını**
kanıtlıyor — `realloc` yeni bir yere geçtiyse bile eski içeriği yeni bloğa kendisi kopyaladı.

Sahiplik açısından belirleyici olan şudur: büyütme öncesi `buyuyen`'in sahibi çağırandı,
büyütme sonrası da hâlâ çağırandır — **yeni bir sahip doğmadı**, yalnız aynı sahibin elindeki
adres değişmiş olabilir. Sayaç bunu doğruluyor: `say_buyut(NULL, ...)` çağrısı yeni bir blok
sayıldığı için ayrılan sayacını bir artırdı, ikinci `say_buyut(buyuyen, ...)` çağrısı ise
**yeni bir ayırma saymadı** — `sayac.c`deki `yeni_blok` denetimi tam bunun için var. Gözlemin
sonunda `say_birak(buyuyen)` çağrısı tek bir bloğu bırakıyor ve fark **0**'a dönüyor.

Kodun yapısı, `yeni` değişkenine dikkat çekiyor: `say_buyut`'un dönüşü doğrudan `buyuyen`'in
üzerine yazılmıyor, önce ayrı bir değişkene alınıyor ve `NULL` olup olmadığı denetleniyor.
Bunun nedeni, başarısız bir yeniden boyutlandırmada **eski bloğun hâlâ geçerli ve hâlâ
çağıranın olması**dır: `realloc` isteği karşılayamazsa orijinal bloğu bırakmaz, olduğu gibi
bırakır ve `NULL` döner. Dönüşü doğrudan `buyuyen = say_buyut(buyuyen, ...);` biçiminde
yazmak, başarısızlık durumunda `buyuyen`'in kendisini `NULL`'a çevirir — o andan sonra eski
bloğa ulaşacak hiçbir işaretçi kalmaz ve blok **sahipsiz** hâle gelir, sızıntı olarak
görünürdü. Bu dersin kodu bu hatayı işlemiyor, çünkü istek burada her zaman karşılanabilecek
kadar küçük; ama akış — ayrı değişkene alma, `NULL` denetimi, yalnız başarıyla üzerine yazma
— tam olarak bu ikinci kalıbı da engelleyecek biçimde kuruldu. **Doğru kalıp ile yanlış
kalıp arasındaki fark, bir satırlık bir atamadır**, ama sonucu bir sahibin bloğu hâlâ
bulabilmesi ile tamamen kaybetmesi arasındaki farktır.

#### Zincirleme Büyütme

Önceki gözlem `buyuyen`'i tek adımda büyüttü. Ama bir dizinin dolarken tekrar tekrar
büyütülmesi de yaygın bir kalıptır — her eleman eklendiğinde bloğu bir eleman daha büyütmek
yerine, dolduğunda iki katına çıkarmak. Bu kalıbın sahiplik açısından ilginç yanı, kaç kez
büyütülürse büyütülsün bloğun hâlâ **tek bir ayırma** sayılmasıdır.

```c
// zincir.c — art arda buyutmenin sayaci yalniz bir kez artirmasi
#include <stdio.h>
#include "sayac.h"

int main(void) {
    char tampon[128];
    int once = ayrilan_sayisi() - birakilan_sayisi();

    int kapasite = 1;
    int *dizi = say_buyut(NULL, kapasite * sizeof *dizi);
    int dolu = 0;

    for (int i = 0; i < 5; i++) {
        if (dolu == kapasite) {
            kapasite *= 2;
            int *yeni = say_buyut(dizi, kapasite * sizeof *dizi);
            if (yeni == NULL) {
                break;
            }
            dizi = yeni;
        }
        dizi[dolu] = i + 1;
        dolu++;
    }

    int toplam = 0;
    for (int i = 0; i < dolu; i++) {
        toplam += dizi[i];
    }

    int ara_fark = (ayrilan_sayisi() - birakilan_sayisi()) - once;
    say_birak(dizi);
    int son_fark = (ayrilan_sayisi() - birakilan_sayisi()) - once;

    snprintf(tampon, sizeof tampon,
             "dolu %d, kapasite %d, toplam %d, buyutme sonrasi fark %d, birakma sonrasi fark %d",
             dolu, kapasite, toplam, ara_fark, son_fark);
    printf("%-30s%s\n", "zincirleme buyutme", tampon);

    return 0;
}
```

```
zincirleme buyutme            dolu 5, kapasite 8, toplam 15, buyutme sonrasi fark 1, birakma sonrasi fark 0
```

**BE2.** Döngü `kapasite`'yi 1 → 2 → 4 → 8 olmak üzere üç kez büyüttü, yani `say_buyut`
toplam dört kez çağrıldı (ilk ayırma dahil). Buna karşın büyütmeler bittiğinde sayaç farkı
hâlâ **1**: `sayac.c`deki `yeni_blok` denetimi yalnız `eski == NULL` olduğu ilk çağrıda
`ayrilan`'ı artırıyor, sonraki üç çağrı aynı bloğun **devamı** sayılıyor. Bu, "Yeniden
Boyutlandırma Yeni Bir Sahip Doğurmaz" bölümünde tek adımda gösterilen kuralın, kaç adıma
yayılırsa yayılsın değişmediğini kanıtlıyor: sahiplik zincirin başında kaç kez el
değiştirdiğine değil, kaç kez **yeni** bir ayırmayla başladığına bakar. `say_birak(dizi)`
çağrısından sonra fark **0**'a dönüyor — zincirin sonunda da, tıpkı başında olduğu gibi, tek
bir sahip vardı.

#### Küçülen Blok

Zincirleme büyütme bloğu hep genişletti; ama `realloc` aynı sözleşmeyle bloğu
**küçültebilir** de. Küçültme, büyütmenin aynadaki görüntüsü değildir: uygulama isterse
bloğu yerinde küçültür, isterse daha küçük bir adrese taşır, ama iki durumda da sahiplik
sorusunun yanıtı değişmez — küçülen blok da hâlâ çağırana aittir.

```c
// kucul.c — realloc ile kucultme de ayni cagiran sahipligini korur
#include <stdio.h>
#include "sayac.h"

int main(void) {
    char tampon[96];
    int once = ayrilan_sayisi() - birakilan_sayisi();

    int *genis = say_buyut(NULL, 10 * sizeof *genis);
    for (int i = 0; i < 10; i++) {
        genis[i] = i;
    }
    int ilk_fark = (ayrilan_sayisi() - birakilan_sayisi()) - once;

    int *dar = say_buyut(genis, 2 * sizeof *dar);
    int korunan_ilk = -1, korunan_ikinci = -1;
    if (dar != NULL) {
        korunan_ilk = dar[0];
        korunan_ikinci = dar[1];
    }
    int kuculme_farki = (ayrilan_sayisi() - birakilan_sayisi()) - once;

    say_birak(dar);
    int son_fark = (ayrilan_sayisi() - birakilan_sayisi()) - once;

    snprintf(tampon, sizeof tampon,
             "ilk eleman %d, ikinci eleman %d, buyume farki %d, kuculme farki %d, birakma sonrasi %d",
             korunan_ilk, korunan_ikinci, ilk_fark, kuculme_farki, son_fark);
    printf("%-30s%s\n", "kuculen blok", tampon);
    return 0;
}
```

```
kuculen blok                  ilk eleman 0, ikinci eleman 1, buyume farki 1, kuculme farki 1, birakma sonrasi 0
```

**BE3.** `dar`, `genis`'in ilk on elemanından yalnız ikisini tutacak kadar küçük ayrıldı;
buna karşın `korunan_ilk` ve `korunan_ikinci` değerleri, küçültmeden önce yazılmış **0** ve
**1**'i aynen taşıyor — `realloc` bloğu küçültürken kalan içeriği bozmuyor, yalnız artık
alanı serbest bırakıyor. Sayaç farkı `ilk_fark`'tan `kuculme_farki`'ya hiç değişmedi: ayırma
tek kaldı, büyüme de küçülme de aynı bloğun devamıydı. Bu, "Yeniden Boyutlandırma Yeni Bir
Sahip Doğurmaz" bölümündeki kuralın yönden bağımsız olduğunu gösteriyor — kural büyütmeye
özgü değil, `realloc`'un boyutu **her iki yönde de** değiştirebilmesine dayanıyor.

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

Boyutu **sıfır** olan bir `malloc` ya da `calloc` çağrısı standart tarafından iki yönde de
bırakılmıştır: uygulama ya `NULL` döndürebilir ya da `free` ile bırakılabilecek, boyutu
sıfır olan geçerli bir işaretçi döndürebilir. Bu, ortama bağlı bir davranıştır ve hangisinin
seçildiği burada **basılmaz** — kaynakta okunan tek şey, çağıranın bu iki olasılığın da
gerçekleşebileceğini bilerek yazması gerektiğidir. Dört ayırma işlevinin **sahiplik**
yönünden verdiği güvence değişmez — dönen işaretçi `NULL` değilse sahibi yine çağırandır —
ama bu güvence, boyutu sıfır olan bir istekte dönüşün ne olacağını söylemez.

Aynı belirsizlik `realloc(blok, 0)` çağrısı için de geçerlidir: bazı uygulamalarda bu çağrı
`free(blok)` ile aynı etkiyi yapıp `NULL` döner, bazılarında boyutu sıfır olan geçerli bir
blok döner. İkisi arasındaki fark, çağıranın bloğu **hâlâ elinde tutup tutmadığını**
etkiler — birinci durumda blok zaten bırakılmıştır, ikinci durumda hâlâ çağıranındır ve ayrı
bir `free` çağrısı gerekir. Bu kararsızlık yüzünden bu kursun kodu `realloc`'u hiçbir yerde
sıfır boyutla çağırmıyor; boyutu sıfıra indirmek istendiğinde blok doğrudan `say_birak` ile
bırakılıyor ve işaretçi ayrı olarak `NULL`'a atanıyor. Sahiplik sorusunun ortama bağlı iki
farklı yanıtı olabildiği bir yerde, ders o soruyu hiç sormamayı tercih ediyor.

#### Özet

- `malloc`, `calloc` ve `realloc` dönen bloğun sahibi her zaman **çağırandır**; bu dersin
  ölçtüğü bellek sınıfı **çağıran**dır.
- `malloc` içeriği sıfırlamaz, `calloc` sıfırlar — bu fark ölçümde değerler doğrudan
  okunarak gösterildi, tahmin edilmedi.
- `realloc` yeni bir sahip doğurmaz; içerik korunur, sayaç yalnız ilk ayırmada artar.
- Başarısız bir `realloc` eski bloğu bırakmaz; dönüşü doğrudan eski değişkenin üzerine
  yazmak eski bloğu sahipsiz bırakır, bu yüzden dönüş önce ayrı bir değişkende denetlenir.
- `malloc(0)` ve `calloc` ile boyutu sıfır olan bir istek uygulamaya bağlıdır ve bu dersin
  ölçümünde basılmaz.

#### Sonraki Adım

Bu ders sahipliğin **nasıl başladığını** ölçtü: bir blok ayrılır ayrılmaz, onu bırakma
yükümlülüğü çağırana geçer. Ama bu yükümlülüğün **nerede yazılı olduğu** henüz sorulmadı —
`say_ayir` ve `say_birak`'ın kendisi hiçbir yerde "bunu sen bırakacaksın" demiyor, bu bilgi
yalnızca işlevin adından ve alışkanlıktan çıkarılıyor. Sıradaki ders bu yükümlülüğü bir
başlık dosyasına yazılı hâle getiriyor ve sahipliğin çağırandan **çağrılana** geçtiği ilk
düzeneği gösteriyor: aynı metni döndüren iki işlevden birinin sahibi bırakan taraf, ötekinin
sahibi çağıran olacak.

### Serbest Bırakma Disiplini

Source: https://academia.sh/tr/kurslar/c-bellek/serbest-birakma-disiplini

Bir işlev bir metni döndürdüğünde, o metnin sahibinin kim olduğu imzada yazmaz — yazan, başlık dosyasındaki yorumdur. Aynı kaydı gösteren iki işlev, biri ödünç verip biri kopya döndürerek, aynı sözleşmenin iki farklı sahibe nasıl bağlanabildiğini gösteriyor.

Önceki ders dört ayırma işlevinin dördünün de aynı sözleşmeyi taşıdığını gösterdi: ayıran
bırakır. Ama bu, sözleşmenin **tek biçimi** değil. Bir işlev bir işaretçi döndürdüğünde, o
işaretçinin sahibinin kim olacağına imza karar vermez — `char *` döndüren iki işlev,
aralarında hiçbir görünür fark olmadan, birbirinden tamamen farklı bir yükümlülük taşıyabilir.
Bu ders, aynı kaydı gösteren iki işlevle bu farkı ölçüyor: biri belleği **modülün içinde**
tutuyor ve yalnız ödünç veriyor, öteki çağırana **yeni bir blok** döndürüyor.

#### Sözleşme Başlık Dosyasında Yazılıdır

Ölçüm, kursun geri kalanında yeniden kullanılan `sahip.h` ve `sahip.c` çiftiyle yapılıyor;
ikisi de sayaçlı sarmalayıcının (`sayac.h`/`sayac.c`) üzerine kurulu, o yüzden aynı birim
içinde onlar da yer alıyor. Kaydın içi (`struct Kayit`in alanları) `sahip.h`de görünmüyor —
yalnızca bir isim (`Kayit`) ve dört işlev imzası var. Sahiplik bilgisi, imzanın **üzerindeki
yorumda**.

```c
// 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
```

```c
// 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;
}
```

```c
// sahip.h — kaydin ici gizli, sahiplik sozlesmesi yorumlarda yazili
#ifndef SAHIP_H
#define SAHIP_H

#include <stddef.h>

typedef struct Kayit Kayit;

/* Cagiran sahiplenir ve kayit_yok_et ile birakir. Ad sinira sigmazsa NULL
   doner; o ana kadar ayrilan bellegi islev kendisi birakir. */
Kayit *kayit_olustur(const char *ad, size_t sinir);

/* Kaydi ve kaydin icindeki ad tamponunu birlikte birakir. */
void kayit_yok_et(Kayit *k);

/* Kayit sahiplenir: donen isaretci kayit yok edilene dek gecerlidir,
   cagiran onu birakmaz. */
const char *kayit_ad(const Kayit *k);

/* Cagiran sahiplenir: donen kopya say_birak ile birakilir. */
char *kayit_ad_kopyasi(const Kayit *k);

#endif
```

```c
// sahip.c — sozlesmenin uygulandigi birim
#include <string.h>
#include "sahip.h"
#include "sayac.h"

struct Kayit {
    char *ad;
    size_t uzunluk;
};

Kayit *kayit_olustur(const char *ad, size_t sinir) {
    Kayit *k = say_ayir(sizeof *k);
    if (k == NULL) {
        return NULL;
    }
    k->uzunluk = strlen(ad);
    if (k->uzunluk >= sinir) {
        say_birak(k);
        return NULL;
    }
    k->ad = say_ayir(k->uzunluk + 1);
    if (k->ad == NULL) {
        say_birak(k);
        return NULL;
    }
    memcpy(k->ad, ad, k->uzunluk + 1);
    return k;
}

void kayit_yok_et(Kayit *k) {
    if (k == NULL) {
        return;
    }
    say_birak(k->ad);
    say_birak(k);
}

const char *kayit_ad(const Kayit *k) {
    return k->ad;
}

char *kayit_ad_kopyasi(const Kayit *k) {
    char *kopya = say_ayir(k->uzunluk + 1);
    if (kopya != NULL) {
        memcpy(kopya, k->ad, k->uzunluk + 1);
    }
    return kopya;
}
```

**BE4.** `kayit_ad` ile `kayit_ad_kopyasi`'nin imzaları neredeyse aynıdır — ikisi de bir
`const Kayit *` alır, ikisi de bir `char` dizisine işaretçi döndürür. Aralarındaki fark
derleyicinin denetleyebileceği hiçbir yerde durmuyor; yalnız yorumda duruyor. Bu, C'nin
bellek sahipliğini nasıl taşıdığının kısa özetidir: **tip sistemi bir yükümlülüğü
zorlamaz, yalnız bir insanın yazdığı ve bir başka insanın okuduğu bir cümle zorlar.**

#### Ödünç Alınan ile Sahiplenilen

`kayit_ad` kaydın içindeki `ad` alanına doğrudan bir işaretçi döndürüyor — yeni bellek
ayırmıyor. Bu işaretçi **ödünçtür**: geçerliliği kaydın kendisine bağlıdır, kayıt
`kayit_yok_et` ile yok edilene kadar sürer. `kayit_ad_kopyasi` ise `say_ayir` ile **yeni**
bir blok ayırıp içine aynı metni kopyalıyor; bu blok kaydın ömründen bağımsızdır ve
sahibi artık çağırandır.

```c
// sozlesme.c — odunc verilen ad ile donen kopyanin karsitligi
#include <stdio.h>
#include <string.h>
#include "sahip.h"
#include "sayac.h"

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, sonra;

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

    Kayit *k = kayit_olustur("sozlesme", 16);

    /* --- cagrilan: modul sahiplenir, cagiran birakmaz --- */
    once = ayrilan_sayisi() - birakilan_sayisi();
    const char *odunc = kayit_ad(k);
    sonra = ayrilan_sayisi() - birakilan_sayisi();
    snprintf(tampon, sizeof tampon, "ad %s, sayac farki %d", odunc, sonra - once);
    gozle("odunc verilen ad", "cagrilan", tampon);

    /* --- cagiran: donen kopyanin sahibi cagirandir --- */
    once = ayrilan_sayisi() - birakilan_sayisi();
    char *kopya = kayit_ad_kopyasi(k);
    int esit = (kopya != NULL) && (strcmp(kopya, odunc) == 0);
    say_birak(kopya);
    sonra = ayrilan_sayisi() - birakilan_sayisi();
    snprintf(tampon, sizeof tampon, "esit metin %d, sayac farki %d", esit, sonra - once);
    gozle("donen kopya", "cagiran", tampon);

    /* --- odunc hala gecerli: kopya birakildi, kayit hala ayakta --- */
    const char *odunc_sonra = kayit_ad(k);
    snprintf(tampon, sizeof tampon, "ad %s, kayittan once mi %d", odunc_sonra, odunc_sonra == odunc);
    gozle("odunc hala gecerli", "cagrilan", tampon);

    /* --- kayit yok edilince odunc de kaydin omrunu takip eder --- */
    once = ayrilan_sayisi() - birakilan_sayisi();
    kayit_yok_et(k);
    sonra = ayrilan_sayisi() - birakilan_sayisi();
    snprintf(tampon, sizeof tampon, "sayac farki %d", sonra - once);
    gozle("kaydin yok edilmesi", "cagrilan", tampon);

    return 0;
}
```

```
gozlem                        sahip     kanit
odunc verilen ad              cagrilan  ad sozlesme, sayac farki 0
donen kopya                   cagiran   esit metin 1, sayac farki 0
odunc hala gecerli            cagrilan  ad sozlesme, kayittan once mi 1
kaydin yok edilmesi           cagrilan  sayac farki -2
```

**BE5.** `odunc verilen ad` gözleminde sayaç farkı **0**'dır — `kayit_ad` hiçbir şey
ayırmadı, hiçbir şey bırakmadı; yalnız var olan bir bloğa işaret eden bir işaretçi
kopyaladı. `donen kopya` gözleminde de fark **0**, ama nedeni farklı: burada bir ayırma
**oldu**, ardından `say_birak(kopya)` ile hemen bırakıldı. İki sıfır, iki farklı öyküyü
gizliyor — biri hiç ayırma yapmadığı için sıfır, öteki ayırdığını bıraktığı için sıfır.
`kaydin yok edilmesi` gözlemindeki **-2** ise beklenen: `kayit_yok_et` iki bloğu (adı ve
kaydın kendisini) bıraktı, hiçbirini yeniden ayırmadı; sayaç farkı negatife döndü çünkü bu
kez ölçülen yalnız bırakma tarafı.

`odunc hala gecerli` gözlemi, ödünç verilen işaretçinin **kaydın ömrüne bağlı** olduğunu
gösteriyor: `kayit_ad_kopyasi` çağrısı ayrı bir blok ayırıp bıraktı, ama kaydın kendisine
hiç dokunmadı — bu yüzden `kayit_ad`'in ikinci çağrısı **aynı** adresi (`odunc_sonra == odunc`)
döndürdü. Ödünç verilen işaretçiyi geçersiz kılan tek şey `kayit_yok_et`'in kendisidir.

#### İki Bağımsız Kopya

`kayit_ad_kopyasi` her çağrıldığında **yeni** bir blok ayırıyor; bu, aynı kayıttan iki kopya
istenirse iki ayrı sahibin doğduğu anlamına gelir. Kopyalardan biri erken bırakılırsa
öteki bundan hiç etkilenmez — çünkü ikisi arasında adresten başka hiçbir bağ yok.

```c
// iki_kopya.c — ayni kayittan alinan iki kopyanin bagimsiz omru
#include <stdio.h>
#include <string.h>
#include "sahip.h"
#include "sayac.h"

int main(void) {
    char tampon[96];
    Kayit *k = kayit_olustur("bagimsiz", 16);

    int once = ayrilan_sayisi() - birakilan_sayisi();
    char *kopya_a = kayit_ad_kopyasi(k);
    char *kopya_b = kayit_ad_kopyasi(k);
    int ara_fark = (ayrilan_sayisi() - birakilan_sayisi()) - once;

    say_birak(kopya_a);
    int b_ayakta_mi = (strcmp(kopya_b, "bagimsiz") == 0);
    int orta_fark = (ayrilan_sayisi() - birakilan_sayisi()) - once;

    say_birak(kopya_b);
    int son_fark = (ayrilan_sayisi() - birakilan_sayisi()) - once;
    kayit_yok_et(k);

    snprintf(tampon, sizeof tampon,
             "iki ayirma sonrasi fark %d, a birakilinca %d, b hala okunuyor %d, ikisi de birakilinca %d",
             ara_fark, orta_fark, b_ayakta_mi, son_fark);
    printf("%-30s%s\n", "iki bagimsiz kopya", tampon);

    return 0;
}
```

```
iki bagimsiz kopya            iki ayirma sonrasi fark 2, a birakilinca 1, b hala okunuyor 1, ikisi de birakilinca 0
```

**BE6.** `kopya_a` ile `kopya_b` aynı kaynaktan (`k->ad`) türedi, ama ikisi de
`kayit_ad_kopyasi` üzerinden **ayrı** bir `say_ayir` çağrısıyla doğdu — bu yüzden sayaç
farkı iki ayırmadan sonra **2**. `say_birak(kopya_a)` çağrısı yalnız `kopya_a`'nın bloğunu
etkiliyor; `kopya_b` hâlâ geçerli ve `strcmp` bunu doğruluyor (`b hala okunuyor 1`). Bu,
`kayit_ad`'in tek bir ödünç işaretçisi döndürmesiyle tam tersi bir örüntü: orada aynı
kaydın iki çağrısı **aynı** adresi verirdi (`odunc_sonra == odunc`), burada aynı kaydın
iki `kayit_ad_kopyasi` çağrısı **farklı** iki blok verir. Sahiplik sınıfı ikisinde de
çağırandır, ama her kopya kendi başına, birbirinden bağımsız bir sahiplik taşır —
birini bırakmak ötekinin sözleşmesini hiç etkilemez.

#### Sınır Aşımında Temizlik

`kayit_olustur`'un başlık dosyasındaki yorumu bir üçüncü olasılığı da kayda geçiriyor: ad
verilen sınıra sığmazsa işlev `NULL` döner, ama o ana kadar ayırdığı belleği **kendisi**
bırakır. Bu, çağrılan sınıfının yalnızca başarılı dönüşte değil, **başarısız** dönüşte de
bir yükümlülük taşıdığını gösteriyor — modül kendi yarım kalan işini kendi temizliyor,
çağırana hiçbir şey bırakmıyor.

```c
// sinir.c — kayit_olustur sinira sigmayinca kendi ayirdigini kendisi birakir
#include <stdio.h>
#include "sahip.h"
#include "sayac.h"

int main(void) {
    char tampon[64];
    int once = ayrilan_sayisi() - birakilan_sayisi();

    Kayit *basarisiz = kayit_olustur("cok-uzun-bir-ad", 4);
    int null_mu = (basarisiz == NULL);
    int fark = (ayrilan_sayisi() - birakilan_sayisi()) - once;

    snprintf(tampon, sizeof tampon, "NULL mu %d, sayac farki %d", null_mu, fark);
    printf("%-30s%s\n", "sinira sigmayan ad", tampon);

    return 0;
}
```

```
sinira sigmayan ad            NULL mu 1, sayac farki 0
```

**BE7.** `"cok-uzun-bir-ad"` on beş karakter uzunluğunda, sınır ise **4** — `kayit_olustur`
`k` için bir blok ayırdıktan sonra uzunluk denetimini başarısız buluyor ve `k->ad`ı hiç
ayırmadan `say_birak(k)` çağırıp `NULL` döndürüyor. Sayaç farkı yine de **0**: tek ayırma,
tek bırakma, ikisi de işlevin kendi içinde oldu. Çağıran taraf `basarisiz`'in `NULL`
olduğunu görüyor ama hangi bloğun ayrılıp hangisinin bırakıldığını hiç bilmiyor — ve
bilmesine gerek de yok, çünkü sözleşme başarısızlık durumunda da modülün üzerinde.

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

`sahip.h`deki yorum iki yönde de bağlayıcıdır. Çağıran `kayit_ad`'in döndürdüğü işaretçiyi
`say_birak` ile bırakırsa, kaydın kendi bırakma çağrısı (`kayit_yok_et` içindeki
`say_birak(k->ad)`) aynı bloğu **ikinci kez** bırakır — bu, tanımsız davranıştır ve bu
derste koşturulmaz. Tersi de kırılabilir: çağıran `kayit_ad_kopyasi`'nin döndürdüğü bloğu
hiç bırakmazsa, blok kaydın ömründen bağımsız olduğu için kaydın yok edilmesi de onu
kurtarmaz — sızar. Sözleşmenin iki yönü de aynı yorumda yazılı, ama derleyici hiçbirini
denetlemez; denetleyen, yalnız kodu okuyan kişidir.

Bu iki kırılma biçiminin ortak noktası, ikisinin de **imzadan görünmemesidir**. `char *
kayit_ad(const Kayit *k)` ile `char *kayit_ad_kopyasi(const Kayit *k)` derleyicinin
gözünde eş değerdedir — ikisi de bir `char` dizisine işaretçi döndürür, ikisi de `const
Kayit *` alır. Çağıran taraf yanlış işlevi yanlış şekilde ele alsa (`kayit_ad`'in dönüşünü
bırakmaya kalksa ya da `kayit_ad_kopyasi`'nin dönüşünü hiç bırakmasa) derleme yine de
başarılı olur; hata yalnız çalışma zamanında, çift bırakma ya da sızıntı olarak ortaya
çıkar. Bu, önceki dersteki `realloc`'un başarısız dönüşünü **üzerine yazmanın** aynı
sınıftan bir hatadır: ikisi de sözdizimsel olarak geçerli, ikisi de derleyicinin
yakalayamadığı bir sahiplik ihlalidir. Aradaki fark kaynaktır — orada hata bir dönüş
değerinin göz ardı edilmesinden, burada bir yorumun okunmamasından doğuyor.

`sahip.c`nin kendi içinde bu iki kırılmaya karşı hiçbir savunması yok; savunmayı
üstlenen, modülün **kullanıcısıdır**. `kayit_olustur` ile `kayit_yok_et` çifti bir
kaydın tüm ömrünü modülün kendi kontrolünde tutuyor — çağıran yalnız bu çifti simetrik
kullandığı sürece güvende. `kayit_ad`'in ödünç verdiği işaretçiyi bir değişkende saklayıp
kaydı yok ettikten sonra o değişkeni okumak da aynı aileden bir hatadır (sarkan
işaretçi); bu ders bunu ayrıca koşturmuyor çünkü zaten M10/K02'nin ömür sorusuna geri
dönüyor — burada asıl konu **kimin bırakacağı**, orada konu **ne zaman bittiği**ydi ve
ikisi burada aynı anda kırılıyor.

**Bu ders, M10/K02'de ölçülen ömür sorusuyla karışmaz.** Orada bir değerin ömrünün
**nerede bittiği** ölçülmüştü ve o yeri **dil** belirliyordu. Burada ölçülen, o ömrü
bitirmekle **kimin yükümlü olduğudur** ve bunu belirleyen dil değil, `sahip.h`deki
**sözleşmedir.** `kayit_ad` ile `kayit_ad_kopyasi` aynı metni verir, ikisinin de ömrü
sonunda biter — ama biri o sonu kaydın sahibine bırakır, öteki çağırana devreder.

#### Özet

- Bir işlevin döndürdüğü işaretçinin sahibi imzadan değil, başlık dosyasındaki **yorumdan**
  anlaşılır; bu dersin ölçtüğü bellek sınıfı **çağrılan**dır.
- `kayit_ad` ödünç verir: sayaç farkı **0**, geçerliliği kaydın ömrüne bağlıdır.
- `kayit_ad_kopyasi` yeni bir blok ayırır: sayaç farkı da **0**, ama bu kez ayırma ve
  bırakma gerçekten oldu — sahibi artık çağırandır.
- Sözleşmeyi kırmak (ödünç alınanı bırakmak ya da kopyayı hiç bırakmamak) çift bırakma ya
  da sızıntı doğurur; ikisi de bu derste koşturulmadı, yalnız tarif edildi.
- Bu dersteki yükümlülük sorusu, M10/K02'deki ömür sorusundan ayrıdır: biri **kim
  bırakacak**ı sorar, öteki **ne zaman biter**i.

#### Sonraki Adım

`kayit_ad_kopyasi`'nin döndürdüğü kopya bırakılmazsa ne olur, bu derste yalnız tarif edildi
— koşturulmadı. Sıradaki ders tam bunu ölçüyor: dönüşü hiç tutulmayan bir kopya, hiçbir
işaretçinin artık ulaşamadığı bir blok bırakır ve bu blok, ne bir hata mesajıyla ne bir
çökmeyle görünür — yalnız sayaç farkıyla.

### Bellek Sızıntıları

Source: https://academia.sh/tr/kurslar/c-bellek/bellek-sizintilari

Bir bellek sızıntısı ne bir hata mesajı verir ne programı çökertir — yalnız ayrılan ile bırakılan arasındaki farkı büyütür. Ders iki yoldan sızıntı üretiyor, ikisini de sayaç farkıyla yakalıyor ve toplamın neden tam 2 çıktığını gösteriyor.

Önceki ders bir sözleşmenin nasıl kırılabileceğini yalnız **tarif etti** — çift bırakma da,
bırakılmayan bir kopya da orada koşturulmadı. Bu ders, sözleşmenin kırılmasının bir türünü
gerçekten koşturuyor: kimsenin bırakmakla yükümlü olmadığı bir blok. Bu, önceki üç sınıftan
(dil, çağıran, çağrılan) hiçbirine girmez — kursun dördüncü ve son sınıfı **sahipsiz**dır.
Sahipsiz sınıf bir tasarım değil, bir kusurdur; ve göründüğü tek yer **sayaç farkıdır.**

#### İki Yoldan Aynı Sonuç

Bir blok iki farklı biçimde sahipsiz kalabilir: ona ulaşan tek işaretçinin üzerine
yazılmasıyla, ya da onu döndüren bir çağrının dönüş değerinin hiç tutulmamasıyla. İkisi de
aynı şeyi üretir — artık hiçbir değişkenden ulaşılamayan bir blok — ama biri bir atama
hatası, öteki bir gözden kaçırmadır.

```c
// 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
```

```c
// 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;
}
```

```c
// sahip.h — kaydin ici gizli, sahiplik sozlesmesi yorumlarda yazili
#ifndef SAHIP_H
#define SAHIP_H

#include <stddef.h>

typedef struct Kayit Kayit;

/* Cagiran sahiplenir ve kayit_yok_et ile birakir. Ad sinira sigmazsa NULL
   doner; o ana kadar ayrilan bellegi islev kendisi birakir. */
Kayit *kayit_olustur(const char *ad, size_t sinir);

/* Kaydi ve kaydin icindeki ad tamponunu birlikte birakir. */
void kayit_yok_et(Kayit *k);

/* Kayit sahiplenir: donen isaretci kayit yok edilene dek gecerlidir,
   cagiran onu birakmaz. */
const char *kayit_ad(const Kayit *k);

/* Cagiran sahiplenir: donen kopya say_birak ile birakilir. */
char *kayit_ad_kopyasi(const Kayit *k);

#endif
```

```c
// sahip.c — sozlesmenin uygulandigi birim
#include <string.h>
#include "sahip.h"
#include "sayac.h"

struct Kayit {
    char *ad;
    size_t uzunluk;
};

Kayit *kayit_olustur(const char *ad, size_t sinir) {
    Kayit *k = say_ayir(sizeof *k);
    if (k == NULL) {
        return NULL;
    }
    k->uzunluk = strlen(ad);
    if (k->uzunluk >= sinir) {
        say_birak(k);
        return NULL;
    }
    k->ad = say_ayir(k->uzunluk + 1);
    if (k->ad == NULL) {
        say_birak(k);
        return NULL;
    }
    memcpy(k->ad, ad, k->uzunluk + 1);
    return k;
}

void kayit_yok_et(Kayit *k) {
    if (k == NULL) {
        return;
    }
    say_birak(k->ad);
    say_birak(k);
}

const char *kayit_ad(const Kayit *k) {
    return k->ad;
}

char *kayit_ad_kopyasi(const Kayit *k) {
    char *kopya = say_ayir(k->uzunluk + 1);
    if (kopya != NULL) {
        memcpy(kopya, k->ad, k->uzunluk + 1);
    }
    return kopya;
}
```

```c
// sizinti.c — uzerine yazma ile yok sayilan donusun karsilastirilmasi
#include <stdio.h>
#include "sahip.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;
    int birinci_fark, ikinci_fark;

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

    /* --- uzerine yazilan isaretci: ilk bloga ulasilamaz --- */
    once = fark();
    int *kayip = say_ayir(sizeof *kayip);
    kayip = say_ayir(sizeof *kayip);
    say_birak(kayip);
    birinci_fark = fark() - once;
    snprintf(tampon, sizeof tampon, "sayac farki %d", birinci_fark);
    gozle("uzerine yazilan isaretci", "sahipsiz", tampon);

    /* --- yok sayilan donus: kopya hic tutulmadan kaybolur --- */
    Kayit *k = kayit_olustur("taslak", 16);
    once = fark();
    if (k != NULL) {
        kayit_ad_kopyasi(k);
    }
    ikinci_fark = fark() - once;
    snprintf(tampon, sizeof tampon, "sayac farki %d", ikinci_fark);
    kayit_yok_et(k);
    gozle("yok sayilan donus", "sahipsiz", tampon);

    printf("toplam sahipsiz sayac farki %d\n", birinci_fark + ikinci_fark);

    return 0;
}
```

```
gozlem                        sahip     kanit
uzerine yazilan isaretci      sahipsiz  sayac farki 1
yok sayilan donus             sahipsiz  sayac farki 1
toplam sahipsiz sayac farki 2
```

**BE8.** `uzerine yazilan isaretci` gözleminde `kayip` iki kez ayrılıyor ama yalnız bir kez
bırakılıyor: ikinci `say_ayir` çağrısı `kayip`'in üzerine yazınca, ilk çağrının döndürdüğü
adresi tutan hiçbir değişken kalmıyor. `say_birak(kayip)` yalnız **ikinci** bloğu
bırakabiliyor — birinciye artık hiçbir yoldan ulaşılamıyor. Sayaç farkı **1**: bir blok
ayrıldı, karşılığı hiç bırakılmadı.

**BE9.** `yok sayilan donus` gözlemi farklı bir yoldan aynı sonuca varıyor. `kayit_ad_kopyasi`
her çağrıldığında yeni bir blok ayırıyor ve onu **döndürüyor** — işlevin kendisi bloğu
bırakmıyor, çünkü sözleşmesi bunu çağırana bırakıyor (`Serbest Bırakma Disiplini` dersi).
Burada dönüş değeri hiçbir değişkene atanmadı; işlev başarıyla çalıştı, blok ayrıldı, ama
ona ulaşan hiçbir işaretçi hiç var olmadı. Sayaç farkı yine **1**. İki gözlem birlikte
**toplam sahipsiz sayac farki 2** veriyor — bu sayı, ölçüm çekirdeğinin tam koşumundaki
son satırla aynı **fark 2**dir; burada aynı sonucu yalnız iki sahipsiz gözlemden topluyor.

#### Sızıntı Tekrarlanınca

Önceki bölümdeki iki gözlem sızıntıyı **tek seferlik** ölçtü: her biri sayaç farkını bir
artırdı. Ama "uzun süre çalışan bir süreçte sayaç sürekli büyür" iddiası henüz yalnız
sözle söylendi, ölçülmedi. Aynı sızdıran düzeneği bir döngü içinde birkaç kez çalıştırmak,
bu iddiayı doğrudan bir sayıya çevirir.

```c
// tekrar.c — ayni sizintinin dongude tekrarlanmasi
#include <stdio.h>
#include "sahip.h"
#include "sayac.h"

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

static void sizdiran_cagri(const Kayit *k) {
    /* donus deger kasten tutulmuyor: her cagri bir blok sizdirir */
    kayit_ad_kopyasi(k);
}

int main(void) {
    char tampon[64];
    Kayit *k = kayit_olustur("dongu", 16);
    int once = fark();

    for (int i = 1; i <= 3; i++) {
        sizdiran_cagri(k);
        int su_an = fark() - once;
        snprintf(tampon, sizeof tampon, "cagri %d sonrasi sayac farki %d", i, su_an);
        printf("%-30s%s\n", "sizinti tekrari", tampon);
    }

    kayit_yok_et(k);
    return 0;
}
```

```
sizinti tekrari               cagri 1 sonrasi sayac farki 1
sizinti tekrari               cagri 2 sonrasi sayac farki 2
sizinti tekrari               cagri 3 sonrasi sayac farki 3
```

**BE10.** Her `sizdiran_cagri` çağrısı `kayit_ad_kopyasi` üzerinden bir blok ayırıyor ve
dönüşü hiç tutmuyor — tıpkı `yok sayilan donus` gözlemindeki gibi, ama burada tek bir çağrı
yerine üç kez üst üste. Sayaç farkı çağrı başına tam **1** artıyor: 1, 2, 3. Bu üç sayı,
"kısa süren bir programda bu fark önemsiz kalabilir; uzun süre çalışan bir süreçte sayaç
sürekli büyür" cümlesinin artık bir gözlem, iddia olmadığını gösteriyor. Döngü üç turda
durduğu için fark üçte duruyor; gerçek bir süreçte böyle bir çağrı saniyede binlerce kez
çalışabilir ve aynı doğrusal artış hiç durmadan sürer — sayaç bu kursta elle tutulduğu için
görünür, gerçek bir programda görünmez ama **olmaz** değildir.

#### Kısmi Sızıntı: Yarım Bırakılan Dizi

Önceki iki gözlem sızıntıyı ya tam yaptı ya da hiç yapmadı — bir çağrının dönüşü ya
tutuldu ya hiç tutulmadı. Ama gerçek kodda daha sinsi bir örüntü de olur: bir dizi bloğun
**çoğu** doğru bırakılır, biri **atlanır**. Toplam sayaç farkı bunu da aynı duyarlılıkla
yakalıyor — kaçının doğru yapıldığından bağımsız, yalnız kaçının **eksik** kaldığını
sayıyor.

```c
// kismi.c — dort kopyadan ucunun birakilip birinin atlanmasi
#include <stdio.h>
#include "sahip.h"
#include "sayac.h"

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

int main(void) {
    char tampon[80];
    enum { ADET = 4 };
    char *kopyalar[ADET];
    Kayit *k = kayit_olustur("kismi", 16);
    int once = fark();

    for (int i = 0; i < ADET; i++) {
        kopyalar[i] = kayit_ad_kopyasi(k);
    }

    for (int i = 0; i < ADET; i++) {
        if (i == 2) {
            continue; /* ucuncu kopya kasten birakilmiyor */
        }
        say_birak(kopyalar[i]);
    }

    int kalan_fark = fark() - once;
    kayit_yok_et(k);

    snprintf(tampon, sizeof tampon,
             "ayrilan %d, birakilan %d, kalan sayac farki %d", ADET, ADET - 1, kalan_fark);
    printf("%-30s%s\n", "kismi birakma", tampon);

    return 0;
}
```

```
kismi birakma                 ayrilan 4, birakilan 3, kalan sayac farki 1
```

**BE11.** Döngü dört kopya ayırdı, ikinci döngü bunlardan üçünü bıraktı ve yalnız `i == 2`
olan kopyayı atladı. `kalan_fark` **1** çıkıyor — tam olarak atlanan kopya sayısı kadar,
ne fazla ne eksik. Bu gözlem, sızıntının "ya hep ya hiç" olmadığını gösteriyor: bir döngü
içindeki dört bırakma çağrısından üçünün doğru çalışması, dördüncüsünün eksikliğini
gizlemiyor, sayaç farkı yine de tam eksik kalan kadar büyüyor. Gerçek bir programda bu
örüntü, bir döngünün erken `continue` ya da `break` ile atladığı bir dalda ortaya çıkar —
kodun geri kalanı tamamen doğru bırakma disiplini uygularken, tek bir kontrol akışı dalı
bu disiplinin dışında kalır.

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

Bu iki gözlemin ikisinde de program **çökmedi**, hiçbir uyarı basmadı, hiçbir işlev `NULL`
döndürmedi. Ayırma işlevleri sözleşmelerini tam olarak yerine getirdi: istenen boyutta bir
blok verdiler. Sorun onların tarafında değil, çağıranın tarafında — bir atama unutuldu ya
da bir dönüş değeri yok sayıldı. Bu yüzden bir bellek sızıntısı, çalışma zamanında **hiçbir
gözlemlenebilir belirti** vermez; yalnız ayrılan ile bırakılan arasındaki fark büyür. Kısa
süren bir programda bu fark önemsiz kalabilir; uzun süre çalışan bir süreçte aynı düzenek
tekrar tekrar işletilirse, sayaç sürekli büyür ve sonunda bellek tükenir.

**Bu, M10/K02'de ölçülen ömür sorusuyla karışmaz.** Orada bir değerin ömrünün nerede
bittiği soruluyordu ve o yeri **dil** belirliyordu — kapsam dışına çıkan bir yerel
değişken otomatik olarak geri alınırdı. Burada bloğun ömrünü bitirecek **hiçbir dil kuralı
yok**; blok, dil tarafından değil, **çağıranın kendi bırakma çağrısıyla** geri alınır. O
çağrı hiç yapılmazsa, dil hiçbir şeyi kendiliğinden bitirmez — bu yüzden sahipsiz sınıf
diğer üçünden farklı olarak bir **eksikliğin** adıdır, bir kuralın değil.

Diğer üç sınıfın her biri, sahibin kim olduğuna bir **cevap** verir: dil sınıfında sahip
kapsamın kendisi, çağıran sınıfında sahip çağıran tarafın kodu, çağrılan sınıfında sahip
modülün içindeki sözleşmedir. Sahipsiz sınıfın verdiği cevap yoktur — çünkü soru hiç
sorulmamıştır. `uzerine yazilan isaretci` gözleminde blok, ayrıldığı anda **çağıran**
sınıfına aitti; `say_ayir`'in döndürdüğü işaretçi kimin sorumluluğunda olduğu konusunda
belirsiz değildi. Belirsizlik sonradan, o işaretçi ikinci bir atamayla ezilince doğdu. Aynı
şey `yok sayilan donus` için de geçerli: `kayit_ad_kopyasi`'nin sözleşmesi net (çağıran
sahiplenir), ama sahiplenecek çağıran tarafında hiçbir değişken o rolü üstlenmedi.
Sahipsizlik bu yüzden dördüncü, **bağımsız** bir sınıf değil; önceden var olan bir
sahiplik ilişkisinin koddaki izinin silinmesidir. Bir bloğun sahibi hiçbir zaman
"sahipsiz" olarak tasarlanmaz — yalnız öyle **bırakılır.**

#### Özet

- Bir bellek sızıntısı iki yoldan doğar: bir işaretçinin üzerine yazılması ya da dönen bir
  değerin yok sayılması; bu dersin ölçtüğü bellek sınıfı **sahipsiz**dır.
- Sızıntı hiçbir hata mesajı ya da çökme üretmez; tek görünür kanıtı **sayaç farkıdır.**
- İki gözlemin her biri sayaç farkını **1** artırdı, toplamı **2** verdi — ölçüm
  çekirdeğinin bağlayıcı son satırındaki fark ile aynı sayı.
- Sahipsiz sınıf bir dil kuralı değil, bir **eksikliktir**: dil hiçbir bırakma çağrısını
  kendiliğinden yapmaz.

#### Sonraki Adım

Bu ders sızıntıyı **sayaç farkıyla** ölçtü, çünkü çekirdeğin kendisi sayaçlı bir
sarmalayıcıydı. Gerçek bir programda böyle bir sayaç elle tutulmaz — bunun yerine bu işi
yapan **bir araç** çalıştırılır. Sıradaki ders, bu dersin sayaçlı sarmalayıcısının aslında
böyle bir aracın yaptığını nasıl modellediğini gösteriyor: `yakalandi` ya da `yakalanmadi`,
başka hiçbir isim geçmeden.

### Bellek Hata Ayıklama Araçları

Source: https://academia.sh/tr/kurslar/c-bellek/bellek-hata-ayiklama-araclari

Bir sızıntıyı elle sayaç tutarak bulmak gerçek bir programda mümkün değildir — bunu yapan bir araç çalıştırılır. Ders, kursun baştan beri kullandığı sayaçlı sarmalayıcının, böyle bir aracın yaptığı tek şeyi zaten modellediğini gösteriyor: her ayırmanın karşılığında bir bırakma var mı, yok mu.

Önceki ders bir sızıntının hiçbir hata mesajı ya da çökme üretmediğini, yalnız sayaç
farkıyla göründüğünü gösterdi. Gerçek bir programda kimse elle `ayrilan_sayisi()` ile
`birakilan_sayisi()`yi karşılaştırmaz — bunu yapan **bir araç** çalıştırılır. Bu dersin
konusu böyle bir aracın kendisi değil; konusu, o aracın **yaptığı tek şeyin** ne olduğu.
Kursun baştan beri kullandığı `sayac.c`, aslında bunu birinci dersten beri modelliyordu.

Bu, önceki dersin doğal devamı. `Bellek Sızıntıları` dersi sahipsiz sınıfın iki türünü
gösterdi ve ikisini de elle tutulan bir sayaç farkıyla ölçtü. Ama gerçek bir programda
kimse `main` içine `ayrilan_sayisi() - birakilan_sayisi()` satırı yazıp sonucu elle
okumaz; program büyüdükçe, yüzlerce ayırma çağrısı arasında hangi birinin karşılıksız
kaldığını elle izlemek pratik değildir. Bu yüzden bu işi otomatikleştiren bir katman
devreye girer — ve bu dersin tek iddiası, o katmanın yaptığı şeyin, kursun baştan beri
kullandığı sayaçlı sarmalayıcıdan **daha karmaşık bir fikre dayanmadığıdır.**

#### Bir Denetim Noktası Neyi Karşılaştırır

Bir aracın yaptığı, özünde tek bir işlemdir: programın belirli bir noktasında (çoğunlukla
çıkışında) o ana kadar yapılan her ayırma çağrısının karşılığında bir bırakma çağrısı olup
olmadığını sayar. Karşılık varsa denetim **temiz** çıkar; yoksa, karşılığı bulunamayan her
blok için bir bulgu bildirilir. Bu kursta bu karşılaştırma, `sayac.c`nin tuttuğu iki sayının
farkıdır — `ayrilan_sayisi()` eksi `birakilan_sayisi()`. Fark sıfırsa **yakalanmadi**, sıfır
değilse **yakalandi**.

```c
// 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
```

```c
// 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;
}
```

**BE12.** `sayac.c` bu kursun ilk dersinden beri değişmeden duruyor ve hiçbir yerde "araç"
sözcüğünü taşımıyor — yalnız iki sayaç ve dört işlev. Ama yaptığı şey, bir denetim
noktasında ayrılanla bırakılanı karşılaştırmaktan ibaret; bu, gerçek bir aracın yaptığı işin
küçültülmüş ve elle kurulmuş hâlidir. Fark, gerçek bir araç bu karşılaştırmayı programın
kaynak koduna hiç dokunmadan yapar; burada aynı karşılaştırma `sayac.c`nin içine **elle**
yazıldı.

#### Dengeli ve Dengesiz Dizi

```c
// denetim.c — bir denetim noktasinin dengeli ve dengesiz diziyi ayirt etmesi
#include <stdio.h>
#include "sayac.h"

static const char *denetle(int fark) {
    return fark != 0 ? "yakalandi" : "yakalanmadi";
}

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

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

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

    /* --- dengeli dizi: her ayirmanin karsiligi var --- */
    once = ayrilan_sayisi() - birakilan_sayisi();
    int *birinci = say_ayir(2 * sizeof *birinci);
    int *ikinci = say_ayir(3 * sizeof *ikinci);
    say_birak(birinci);
    say_birak(ikinci);
    fark_sonra = (ayrilan_sayisi() - birakilan_sayisi()) - once;
    snprintf(tampon, sizeof tampon, "denetim noktasi: %s", denetle(fark_sonra));
    gozle("dengeli dizi", tampon);

    /* --- dengesiz dizi: bir ayirmanin karsiligi yok --- */
    once = ayrilan_sayisi() - birakilan_sayisi();
    int *ucuncu = say_ayir(2 * sizeof *ucuncu);
    int *dorduncu = say_ayir(3 * sizeof *dorduncu);
    say_birak(ucuncu);
    /* dorduncu bilerek birakilmiyor: dengesiz dizi burada kuruluyor */
    fark_sonra = (ayrilan_sayisi() - birakilan_sayisi()) - once;
    snprintf(tampon, sizeof tampon, "denetim noktasi: %s", denetle(fark_sonra));
    gozle("dengesiz dizi", tampon);

    say_birak(dorduncu);
    return 0;
}
```

```
gozlem                        kanit
dengeli dizi                  denetim noktasi: yakalanmadi
dengesiz dizi                 denetim noktasi: yakalandi
```

**BE13.** `dengeli dizi` gözleminde iki ayırmanın ikisi de kendi bloğu içinde bırakıldı;
denetim noktasındaki fark **0** ve sonuç **yakalanmadi**. `dengesiz dizi` gözleminde ise
`dorduncu` o denetim noktasına gelene kadar bırakılmadı — kod bunu kendisi, yorum satırıyla
işaretleyerek yaptı, çünkü bir aracın gerçekte ne bulduğunu göstermenin tek tanımlı yolu bu.
Fark **1**e çıktı ve sonuç **yakalandi**. Denetim noktasından sonra `dorduncu` yine de
bırakıldı — ders, tanımlı olmayan bir durumu (programı sızıntılı bitirmeyi) arkasında
bırakmıyor.

#### Üç Ardışık Denetim Noktası

Önceki bölümdeki iki gözlem birbirinden **bağımsız** iki dizi kurdu — her biri kendi
`once` değerinden başladı. Gerçek bir programda denetim noktaları böyle yalıtılmış
durmaz; art arda gelir ve her biri yalnız **kendisinden önceki** noktadan bu yana olan
aralığı görür. Bu, bir sızıntının hangi denetim noktasında yakalandığının, sızıntının
nerede oluştuğuyla aynı yer olduğunu gösterir — ne bir önceki noktada, ne bir sonrakinde.

```c
// ardisik.c — sizintinin tam olustugu denetim noktasinda yakalanmasi
#include <stdio.h>
#include "sayac.h"

static const char *denetle(int fark) {
    return fark != 0 ? "yakalandi" : "yakalanmadi";
}

int main(void) {
    char tampon[64];
    int nokta_once = ayrilan_sayisi() - birakilan_sayisi();

    /* --- birinci nokta: temiz bir ayirma-birakma cifti --- */
    int *a = say_ayir(sizeof *a);
    say_birak(a);
    int fark1 = (ayrilan_sayisi() - birakilan_sayisi()) - nokta_once;
    snprintf(tampon, sizeof tampon, "nokta 1: %s", denetle(fark1));
    printf("%-30s%s\n", "ardisik denetim", tampon);
    nokta_once = ayrilan_sayisi() - birakilan_sayisi();

    /* --- ikinci nokta: bilerek birakilmayan bir blok --- */
    int *b = say_ayir(sizeof *b);
    int fark2 = (ayrilan_sayisi() - birakilan_sayisi()) - nokta_once;
    snprintf(tampon, sizeof tampon, "nokta 2: %s", denetle(fark2));
    printf("%-30s%s\n", "ardisik denetim", tampon);
    nokta_once = ayrilan_sayisi() - birakilan_sayisi();

    /* --- ucuncu nokta: b'den bagimsiz, kendi icinde dengeli yeni bir cift --- */
    int *c = say_ayir(sizeof *c);
    say_birak(c);
    int fark3 = (ayrilan_sayisi() - birakilan_sayisi()) - nokta_once;
    snprintf(tampon, sizeof tampon, "nokta 3: %s", denetle(fark3));
    printf("%-30s%s\n", "ardisik denetim", tampon);

    say_birak(b);
    return 0;
}
```

```
ardisik denetim               nokta 1: yakalanmadi
ardisik denetim               nokta 2: yakalandi
ardisik denetim               nokta 3: yakalanmadi
```

**BE14.** `b` ikinci noktada ayrılıp o noktaya kadar hiç bırakılmadı, bu yüzden
**nokta 2** sonucu `yakalandi`. Üçüncü nokta ise `b`'den tamamen bağımsız, kendi içinde
dengeli yeni bir çift (`c`) kuruyor — kendi aralığında bir ayırmanın karşılığında bir
bırakma var, bu yüzden **nokta 3: yakalanmadi**. `b`'nin sızıntısı ikinci noktadan sonra
hiçbir yerde yeniden raporlanmıyor; üçüncü nokta onu bir daha saymıyor, çünkü her nokta
yalnızca **kendi aralığında** karşılıksız kalan ayırmaları görür, daha önce zaten
raporlanmış bir aralığı tekrar taramaz. Bu üç sonuç birlikte önemli bir şeyi gösteriyor:
bir sızıntı gerçek zamanda tam **hangi aralıkta** oluştuysa, denetim de tam o aralıkta
yakalar — ne bir önceki noktada (henüz ayrılmamıştı), ne bir sonraki noktada (o aralığın
konusu değil). `b` sonunda yine `say_birak` ile bırakılıyor, ama bu bırakma hiçbir
noktanın ölçtüğü aralığın içinde değil — kod yalnız programı sızıntılı bitirmemek için
bunu yapıyor, denetim anlatısının bir parçası olarak değil.

#### Gecikmiş Bir Bırakma Yeni Bir Sızıntıyı Maskeleyebilir

Üç ardışık noktanın gösterdiği düzenek bir varsayıma dayanıyordu: her aralıktaki ayırma ve
bırakma çağrıları o aralığın **kendi olayları**. Ama gerçek bir programda bir blok, bir
denetim noktasında ayrılıp başka bir noktada bırakılabilir — ve bu, sayaç farkının aynı
aralıkta oluşan **başka** bir sızıntıyı sayısal olarak dengeleyip gizlemesine yol açabilir.
Bu, sayaç tabanlı denetimin bir kusuru değil, **sınırıdır**: denetim yalnız net farkı görür,
hangi bloğun hangi olaya ait olduğunu görmez.

```c
// gecikmis.c — onceki araliktan tasinan bir birakmanin yeni bir sizintiyi maskelemesi
#include <stdio.h>
#include "sayac.h"

static const char *denetle(int fark) {
    return fark != 0 ? "yakalandi" : "yakalanmadi";
}

int main(void) {
    char tampon[64];
    int nokta_once = ayrilan_sayisi() - birakilan_sayisi();

    /* --- A noktasi: d ayriliyor, bu araliktan hic birakilmiyor --- */
    int *d = say_ayir(sizeof *d);
    int farkA = (ayrilan_sayisi() - birakilan_sayisi()) - nokta_once;
    snprintf(tampon, sizeof tampon, "nokta A: %s", denetle(farkA));
    printf("%-30s%s\n", "gecikmis birakma", tampon);
    nokta_once = ayrilan_sayisi() - birakilan_sayisi();

    /* --- B noktasi: yeni bir sizinti (e) olusuyor, ama d de burada birakiliyor --- */
    int *e = say_ayir(sizeof *e);
    say_birak(d);
    int farkB = (ayrilan_sayisi() - birakilan_sayisi()) - nokta_once;
    snprintf(tampon, sizeof tampon, "nokta B: %s", denetle(farkB));
    printf("%-30s%s\n", "gecikmis birakma", tampon);

    say_birak(e);
    return 0;
}
```

```
gecikmis birakma              nokta A: yakalandi
gecikmis birakma              nokta B: yakalanmadi
```

**BE15.** A noktası, `d`'nin o aralıkta bırakılmadığını doğru biçimde `yakalandi` olarak
bildiriyor — tıpkı önceki bölümdeki nokta 2 gibi. B noktasında ise iki şey aynı anda
oluyor: `e` yeni ayrılıyor ve **hiç bırakılmıyor** bu aralıkta, üstelik `d` de (önceki
aralıktan kalan borç) tam bu aralıkta bırakılıyor. Sayaç farkı bu iki olayı ayrı ayrı
görmüyor, yalnız toplamlarını görüyor: `+1` (yeni ayrılan `e`) ile `-1` (geç bırakılan
`d`) birbirini götürüyor ve `farkB` **0** çıkıyor — sonuç **yakalanmadi**, oysa bu aralıkta
gerçekten yeni bir blok (`e`) sızdırılmış durumda. `e` yalnızca `main`'in sonunda, hiçbir
denetim noktasının ölçmediği bir yerde bırakılıyor. Bu, bir sayaç farkının **sıfır**
olmasının, o aralıkta hiçbir sorun olmadığı anlamına gelmediğini gösteriyor — yalnız
ayrılan ile bırakılanın **sayıca** eşitlendiği anlamına gelir. Gerçek araçların çoğu bu
sınırı, yalnız sayıyı değil her bloğun kendi kimliğini (hangi satırda ayrıldığını) izleyerek
aşar; bu kurs böyle bir kimlik izlemiyor, çünkü konusu aracın kendisi değil, aracın
dayandığı temel fikir.

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

Bir denetim noktası yalnız **sayar** — hangi bloğun hangi satırda ayrıldığını, o bloğun
programın hangi bölümüne ait olduğunu, ya da bloğun içeriğinin ne olduğunu bilmez. Bildiği
tek şey, karşılığı bulunamayan ayırma çağrısının **sayısıdır**. Bu yüzden bu kursta bir
aracın verdiği yanıt yalnız iki değerden biri olarak yazılıyor: **yakalandi** ya da
**yakalanmadi**. Bu ikilik dışında hiçbir çıktı biçimi, hiçbir bayrak, hiçbir araç adı bu
derste geçmiyor — çünkü hangi aracın hangi çıktıyı ürettiği ortama bağlıdır, ölçülen
düzenek değildir.

Bir aracın **bulamadığı** şey de önemlidir: bir işaretçinin üzerine yazılmadan önce eski
değerinin bir yere kaydedilip kaydedilmediğini, ya da bir bloğun sahiplik sözleşmesinin
`sahip.h`deki yorumla uyuşup uyuşmadığını bir denetim noktası bilemez — bunlar kaynak
kodun okunmasını gerektirir. Denetim noktası yalnız **sayının kendisini** doğrular; sayının
neden o değere ulaştığını açıklamaz.

Araç adının ve çıktı biçiminin bu derste hiç geçmemesinin tek nedeni özen değil; bir
gerekliliktir. Aynı denetim fikri, farklı ortamlarda farklı araçlarla, farklı çıktı
biçimleriyle uygulanabilir — bazısı programı ayrı bir süreç içinde çalıştırır, bazısı
ayırma işlevlerinin kendisini değiştirir, bazısı yalnız çıkıştaki özeti yazar. Bu kurs
hangi ortamda koşacağını bilemeyeceği için, ölçtüğü şeyi ortamdan bağımsız tek bir
düzeneğe indirgiyor: bir sayı ve o sayının sıfır olup olmadığı. `yakalandi` ile
`yakalanmadi` bu yüzden bir basitleştirme değil, dersin **gerçek konusunun** kendisidir.

#### Özet

- Bir bellek hata ayıklama aracının yaptığı tek şey, bir denetim noktasında ayrılan ile
  bırakılan sayısını karşılaştırmaktır; bu dersin ölçtüğü bellek sınıfı yine
  **sahipsiz**dır.
- `sayac.c`, kursun ilk dersinden beri bu karşılaştırmayı elle yapıyordu; bu ders bunu
  ilk kez adlandırdı.
- Dengeli bir dizide denetim noktası **yakalanmadi**, dengesiz bir dizide **yakalandi**
  sonucunu verdi; ikisi dışında üçüncü bir sonuç yok.
- Hiçbir araç adı, çıktı biçimi ya da bayrak bu derste geçmedi — bunlar ortama bağlıdır,
  ölçülen düzenek değildir.

#### Sonraki Adım

Bu ders belleği **modüller arasında** dolaşırken izledi — ayrılan bir bloğun kimin elinde
olduğu, denetim noktasına kadar nasıl değiştiği. Dinamik Bellek konusunun son dersi ise geri, en başa
dönüyor: dört `dil` gözlemi ile `ayir ve birak` gözlemini yan yana koyup, bir bloğun
**hangi bölgede** durduğunun — otomatik, statik ya da dinamik — sahiplik sorusuyla nasıl
örtüştüğünü gösteriyor.

### Bellek Düzeni

Source: https://academia.sh/tr/kurslar/c-bellek/bellek-duzeni

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.

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ı.

```c
// 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
```

```c
// 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;
}
```

```c
// 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 **dil**dir; ö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.

```c
// 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.

```c
// 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ı **dil**dir.
- 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.

## Sağlamlık

Tanımsız davranışın neden var olduğu ve yerine tanımlı karşılığın nasıl yazıldığı, dönüş kodu ve hata numarasıyla hata bildirimi, uzunluğu çağıranın verdiği sınır denetimi, ve uygulamaya bağlı davranışların taşınabilir koddan yalıtılması.

### Tanımsız Davranış

Source: https://academia.sh/tr/kurslar/c-bellek/tanimsiz-davranis

Sarkan işaretçi okumak, bırakılmış belleği kullanmak, bir bloğu iki kez bırakmak ve dizinin sınırı dışına erişmek dördü de tanımsız davranıştır; hiçbiri koşturulmaz, ders her birini düzyazıda tarif eder ve yanına tanımlı karşılığını koşturarak gösterir.

Önceki ders dört bölgeyi tek eksende topladı: yığıt, statik alan, dizgi değişmezi ve öbek —
hepsinin ortak noktası, ömrünü kimin belirlediğinin kaynakta yazılı olmasıydı. Bu ders o
sözleşmenin **kırıldığı** anı ölçüyor. Sözleşme "bu bloğu kim bırakacak" sorusuna bir yanıt
veriyordu; sözleşme kırılınca ortaya çıkan şey bir hata mesajı değil, **tanımsız davranıştır**
(undefined behavior) — ve tanımsız davranışın en önemli özelliği, ölçülemeyecek olmasıdır.

Bu son cümle bir uyarı değil, dersin sınırını çizen bir tanımdır. Aşağıdaki dört bölüm
tanımsız davranışın dört ayrı kaynağını anlatıyor — sarkan işaretçi, bırakılmış bellek,
çift bırakma, sınır dışı erişim — ama hiçbiri kaynağın kendisini koşturmuyor. Her bölümün
yanında koşan kod, o kaynağın **yerine geçen tanımlı karşılıktır**: sözleşme kırılmadan önce
araya giren bir denetim satırı. Bu ayrım kurs boyunca hep aynıdır — ölçülen şey her zaman
tanımlı davranıştır — ama bu derste özellikle önemlidir, çünkü ölçülmeyen tarafın adı da
düzyazıda açıkça geçiyor.

#### Tanımsız Davranış Neden Var

C standardı her ifadenin anlamını tanımlamaz. Bir kısım ifade **tanımlıdır**: `int
toplam = a + b;` her zaman aynı kuralla çalışır. Bir kısmı **uygulamaya bağlıdır**
(implementation-defined): derleyici bir seçim yapar ve o seçimi belgeler. Ama bir kısım
ifade **tanımsızdır**: standart onun ne yapacağını hiç söylemez — derleyici istediği gibi
davranabilir, hatta hiç davranmayabilir.

Bu boşluk bir eksiklik değil, bir tasarım kararıdır. Derleyici, bir programın tanımsız
davranışa **hiç girmediğini varsayarak** optimizasyon yapar. Örneğin bir işaretçinin her
zaman geçerli bir bloğu gösterdiğini varsayabilir, çünkü geçersiz bir bloğu göstermesi zaten
tanımsızdır ve standart bu durumda hiçbir garanti vermez. Bu varsayım sayesinde derleyici
gereksiz denetimleri silebilir, sırayı değiştirebilir, bir değeri yeniden hesaplamak yerine
önbelleğe alabilir. Varsayım yanlış çıkarsa — yani program gerçekten tanımsız davranışa
girerse — üretilen kod hâlâ derlenir ve hâlâ **bir şey** yapar, ama o şeyin ne olduğu
standardın konusu değildir.

Bu varsayımın etkisi derleyici sürümüyle, optimizasyon düzeyiyle ve hatta çevredeki
kodun şekliyle değişir; aynı tanımsız ifade bir derlemede fark edilmeden geçebilir, bir
başkasında programın tamamen farklı bir yol izlemesine yol açabilir. Standart bu iki
sonucu da eşit derecede **geçerli** sayar, çünkü ikisi de "tanımsız" sözünün kapsamındadır.
Bu yüzden tanımsız davranışı bulmanın yolu programı çalıştırıp "hata verdi mi" diye bakmak
değildir — hata vermemesi hiçbir şey kanıtlamaz, yalnız o **belirli** koşumda, o **belirli**
derleyicide bir şey gözlenmediğini gösterir.

Ölçüm çekirdeğinin dört sınıfı arasında bu dersin konusu **sahipsiz** bellektir: sözleşme
kırıldığında ne dil, ne çağıran, ne de çağrılan artık o bloğa tanımlı bir yoldan
erişemiyordur — blok hâlâ orada durabilir, ama onu güvenle kullanacak hiçbir taraf kalmaz.
Bu yüzden aşağıdaki dört kaynağın hiçbiri "kim sahipleniyor" sorusuna doğrudan bir yanıt
vermez; hepsi, sahiplik sözleşmesi zaten çiğnendikten **sonraki** anı gösterir.

Bu yüzden "çalışıyor görünmesi" hiçbir kanıt taşımaz. Bir sonraki bölüm, bu depoda ölçüm
çekirdeğinin dört bilerek koşturmadığı kaynağı tek tek ele alıyor; her birinde önce sözleşme
neden kırıldığı düzyazıyla anlatılıyor, sonra yanına **tanımlı** bir karşılığı koşturuluyor.

#### Sarkan İşaretçiyi Okumak

Bir blok bırakıldıktan sonra ona işaret eden işaretçi **sarkar** (dangling): işaretçinin
kendisi hâlâ eski adresi tutar, ama o adresteki bellek artık ayırıcıya geri verilmiştir.
Sarkan bir işaretçiyi dolaylamak (`*p`) — okumak amacıyla bile olsa — tanımsız davranıştır.
Bellek fiilen hâlâ aynı içeriği taşıyor gibi görünebilir, çünkü ayırıcı o bloğu henüz başka
bir isteğe vermemiş olabilir; bu **rastlantıdır**, garanti değildir. Aynı program bir sonraki
koşumda, ya da aynı koşumda bir sonraki ayırma isteğinden sonra, o adresi tamamen farklı bir
bloğa verebilir.

Ders bu okumayı hiç koşturmuyor. Yerine, sarkan işaretçinin **oluşmasını engelleyen** tanımlı
kalıbı koşturuyor: bir blok bırakıldığı anda işaretçiye `NULL` atamak ve her kullanımdan önce
bunu denetlemek.

```c
// koru.c — sarkan isaretcinin olusmasini engelleyen tanimli karsilik
#include <stdlib.h>

const char *sarkan_karsiligi(void) {
    int *blok = malloc(sizeof *blok);
    if (blok != NULL) {
        *blok = 9;
    }
    free(blok);
    blok = NULL;
    return (blok == NULL) ? "isaretci bos, dolaylama yapilmadi" : "erisildi";
}
```

Bu karşılığın ölçümü, dersin tek `main`'ini içeren son kod bloğunda, öbür üç karşılıkla
birlikte gösteriliyor.

Dikkat edilmesi gereken nokta şu: `blok`'u `NULL`'a çekmek, işaretçinin **kendisini**
değiştirir, işaret ettiği belleği değil — o bellek `free` çağrısıyla zaten ayırıcıya geri
döndü. Denetim yalnız aynı işaretçi değişkeninin bir daha kullanılmasını engelliyor; aynı
adresi tutan **başka** bir işaretçi varsa (örneğin aynı bloğa işaret eden ikinci bir
değişken), o ikinci işaretçi hâlâ sarkar ve bu kalıp onu korumaz. Kalıbın kapsamı, tam
olarak denetlediği işaretçi değişkeniyle sınırlıdır.

#### Bırakılmış Belleği Kullanmak

Bırakılmış belleği kullanmak (use-after-free), sarkan işaretçiyi okumaktan biraz daha
geniştir: bloğa **yazmayı** da kapsar. `free` çağrısından sonra aynı bloğa `*p = değer;`
yazmak, ayırıcının o bellek bölgesini yeniden kullanıma açmış olabileceği anlamına gelir —
yazma, artık ayırıcının kendi defterine ait olan baytların üzerine gidebilir ve ayırıcının
iç tutarlılığını bozabilir. Bu da tanımsızdır ve bir hata iletisi vermeyebilir; program bir
süre daha "normal" çalışabilir.

Tanımlı karşılığı, blokla ilişkilendirilmiş bir **geçerlilik bayrağı** taşımaktır: blok
bırakıldığında bayrak düşürülür ve her yazma bu bayrağa bakar.

```c
// gecerlilik.c — birakma sonrasi yazmayi bayrakla engelleyen kalip
#include <stdlib.h>

const char *kullanim_karsiligi(void) {
    int *ikinci = malloc(sizeof *ikinci);
    int gecerli = (ikinci != NULL);
    if (gecerli) {
        *ikinci = 4;
    }
    free(ikinci);
    ikinci = NULL;
    gecerli = 0;
    if (gecerli) {
        *ikinci = 7;
    }
    return gecerli ? "yazildi" : "yazilmadi, bayrak dustu";
}
```

Bu karşılığın ölçümü de aynı son kod bloğunda gösteriliyor. Bayrağın yanında işaretçinin
kendisi de `free`'den hemen sonra `NULL`'a çekiliyor. Bu satır süs değildir: o olmadan, uyarıları
açık bir derleme `ikinci`'nin `free` sonrasında hâlâ metinde geçtiğini görüp uyarı basar — dal
hiç çalışmasa bile, çünkü derleyici dalın çalışmayacağını her zaman kanıtlayamaz. Derleyicinin
kendisi bile bırakılmış bir işaretçiyi gözünde tutmaya devam eder; bayrak dalı kapatır,
`NULL`'a çekme adın bırakılmış bir adres taşımasını önler ve ikisi birlikte bloğu
uyarısız derletir.

Bayrağın yeri de önemlidir: bayrak, koruduğu işaretçiyle **aynı ömre** sahip olmalıdır.
Bayrak yerel bir değişkense ve işaretçi bir işlevden diğerine geçiyorsa, bayrak işaretçiyle
birlikte taşınmadığı sürece çağrılan taraf işaretçinin hâlâ geçerli olup olmadığını bilemez
— bu da geçerlilik denetiminin kendisinin, denetlediği bloktan **ayrı bir sahiplik**
taşıdığı anlamına gelir; bayrağı kim güncelleyecek, kimin sorumluluğundadır.

#### Çift Bırakma

Aynı bloğu iki kez `free`'ye vermek de tanımsızdır. Ayırıcı, bir bloğu bıraktıktan sonra o
adresi kendi defterine (serbest liste) ekler; ikinci `free` çağrısı aynı adresi bu deftere
ikinci kez eklemeye çalışır ve deftere ait iç yapıyı bozabilir. Sonuç, ayırıcının bir sonraki
`malloc` çağrısında görünür ya da hiç görünmez — ikisi de standardın söylediği bir şey değil.

Tanımlı karşılığı, bir bloğun bırakıldığını da bir bayrakla işaretlemek ve ikinci bırakma
isteğini bu bayrakla **atlamaktır**.

```c
// tekbirak.c — cift birakmayi bayrakla engelleyen kalip
#include <stdlib.h>

const char *tek_birakma(void) {
    int *ucuncu = malloc(sizeof *ucuncu);
    int birakildi = 0;

    if (!birakildi) {
        free(ucuncu);
        birakildi = 1;
    }
    if (!birakildi) {
        free(ucuncu);
        birakildi = 1;
    }
    return birakildi ? "bir kez birakildi" : "birakilmadi";
}
```

İkinci `birakildi` denetimi burada **hiç çalışmayacak** — kod satırı olarak var, ama
`birakildi` bayrağı o noktada zaten doğru olduğu için koşulu hiç sağlamayacak. Bu, dersin
başında kurulan kuralın somut hâlidir: tanımsız yolu gösteren satır kaynakta durabilir, yeter
ki hiç yürütülmesin. Ölçümü yine son kod bloğunda.

Çift bırakma, sahiplik açısından bakıldığında ilginç bir durumdur: blok bırakıldığı anda
sahipliği zaten **hiç kimseye** devretmiştir — ne dil, ne çağıran, ne çağrılan onu artık
sahiplenmez. İkinci `free` çağrısı bu boşluğa yapılan bir çağrıdır; ayırıcı o adresi
tanımaz ya da yanlış tanır, çünkü elinde o adresle ilgili artık **geçerli** bir kayıt
yoktur. Tek-bırakma bayrağı bu boşluğu doldurmuyor, yalnız oraya bir daha girilmesini
engelliyor.

#### Sınır Dışı Erişim

Bir dizinin `[0, uzunluk)` aralığının dışına erişmek — `dizi[uzunluk]` ya da daha ötesi —
tanımsızdır. Bellek fiziksel olarak orada bir şey içerebilir, ama o bayt dizinin bir parçası
değildir; derleyici, bir dizi erişiminin her zaman sınırlar içinde kaldığını varsayarak sınır
denetimlerini silebilir. Sınır dışı bir okuma rastgele bir değer, sınır dışı bir yazma başka
bir değişkenin verisini bozabilir.

Tanımlı karşılığı, erişimden önce **istenen indeksi bilinen uzunlukla karşılaştırmaktır**.

```c
// sinirli.c — indeksi uzunlukla karsilastirip erisimi tanimli tutan kalip
const char *sinirli_erisim(int istenen) {
    int dizi[4] = {10, 20, 30, 40};
    int uzunluk = 4;
    if (istenen >= 0 && istenen < uzunluk) {
        (void)dizi[istenen];
        return "erisildi";
    }
    return "reddedildi, sinir disi";
}
```

`istenen < uzunluk` denetimi C'nin kendisinde yoktur — dizi erişimi (`dizi[i]`) dilde her
zaman sessizce izin verir, ne çalışma zamanında ne derleme zamanında bir denetim yapılır.
Denetim tamamen **programcının eklediği** bir satırdır; eklenmezse dizinin ötesine geçen bir
indeks derlenir ve tanımsız davranışa girer.

Bu, öbür üç kaynaktan farklı bir noktayı gösteriyor: sarkan işaretçi ve bırakılmış bellek
bir **zamanlama** sorunudur — blok bir noktada geçerliyken sonra geçersiz olur. Sınır dışı
erişim ise zamanlamadan bağımsızdır; dizi hiç bırakılmamış, hâlâ tamamen geçerli olsa bile
yanlış indeks tanımsız davranışa girer. Ortak nokta, ikisinde de derleyicinin **hiçbir**
çalışma zamanı denetimi eklememesidir — ne zamanlamayı, ne indeksi C'nin kendisi izler.

#### Dört Karşılığın Birlikte Koşturulması

Dördü de tanımsız değil, tanımsızlığı **önleyen** kalıplardır; hepsi tek bir `main`'de,
sırayla koşturuluyor.

```c
// olcum.c — dordunun de tanimli karsiligi tek main'de olculuyor
#include <stdio.h>

const char *sarkan_karsiligi(void);
const char *kullanim_karsiligi(void);
const char *tek_birakma(void);
const char *sinirli_erisim(int istenen);

static void gozle(const char *ad, const char *sonuc) {
    printf("%-28s%s\n", ad, sonuc);
}

int main(void) {
    printf("%-28s%s\n", "gozlem", "sonuc");
    gozle("sarkan isaretci", sarkan_karsiligi());
    gozle("birakilmis bellek", kullanim_karsiligi());
    gozle("cift birakma denemesi", tek_birakma());
    gozle("gecerli indeks", sinirli_erisim(2));
    gozle("sinir disi indeks", sinirli_erisim(5));
    return 0;
}
```

```text
gozlem                      sonuc
sarkan isaretci             isaretci bos, dolaylama yapilmadi
birakilmis bellek           yazilmadi, bayrak dustu
cift birakma denemesi       bir kez birakildi
gecerli indeks              erisildi
sinir disi indeks           reddedildi, sinir disi
```

**SL1.** `blok == NULL` denetimi burada sarkan işaretçiyi **önlüyor**, sarkan işaretçinin
kendisini ölçmüyor. Tanımsız davranışın ölçülebilir bir "sonucu" yoktur; ölçülebilen tek şey,
ona hiç girilmediğinin kanıtıdır — bu yüzden çıktı satırı "dolaylama yapılmadı" diyor, "değer
şu çıktı" demiyor.

**SL2.** Bayrağın kendisi sıradan bir `int`'tir; C'nin ayrı bir "geçerlilik" tipi yoktur.
Güvence, tipten değil, **her yazma noktasının bayrağı denetlemesi disiplininden** gelir —
derleyici bu disiplini zorlamaz, denetlemeyi unutan bir satır sessizce derlenir.

**SL3.** `tek_birakma` içindeki ikinci `birakildi` denetimi koşumda hiç çalışmadı — çıktı
tek bir "bir kez birakildi" satırı verdi, ikinci bir bırakmanın izi yok.

**SL4.** Son iki satır aynı işlevin aynı diziye iki farklı `istenen` değeriyle çağrılışıdır:
geçerli indeks dizinin içinde kalıp erişiliyor, sınır dışı indeks erişilmeden reddediliyor —
ikisi de tanımlı, ikisi de dizinin ötesine hiç dokunmuyor.

Dört satırın da ortak noktası, hiçbirinin bir dilin ya da bir çalışma zamanının garantisine
dayanmamasıdır. `sarkan_karsiligi` işaretçiyi kendi elleriyle `NULL`'a çeker, `kullanim_karsiligi`
bayrağı kendi elleriyle düşürür, `tek_birakma` ikinci bırakmayı kendi elleriyle atlar,
`sinirli_erisim` sınırı kendi elleriyle karşılaştırır — dördü de aynı bloğu **sahiplenen bir
taraf** olmadığı için, kalan tek güvence kaynakta yazılı denetimdir. Bu, önceki dersin dört
sınıfından (dil, çağıran, çağrılan, sahipsiz) hiçbirinin burada devrede olmadığı anlamına
gelmez; tam tersine, **sahipsiz** sınıfının ne anlama geldiğini tam da bu dört örnek
somutlaştırıyor: blok bir noktada geçerliliğini kaybettiğinde, dil de çağıran da çağrılan da
onu artık kendiliğinden korumaz. Koruma varsa, onu koyan kod satırıdır — derleyici değil,
çalışma zamanı kütüphanesi değil, tip sistemi hiç değil. Bu yüzden sahipsiz sınıfının bu
dersteki karşılığı, bir önceki dersteki dil/çağıran/çağrılan sınıflarının **yerini almaz**;
onların hepsinin ortak bir zayıf noktasını, sözleşme kırıldığı andaki durumu, ayrıca ele alır.

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

Tanımsız davranış bir hata mesajı değildir. Yukarıdaki dört karşılık her koşumda birebir aynı
çıktıyı veriyor — ama bu tutarlılık, karşılıklarının **tanımlı olmasından** geliyor, ilgili
tanımsız işlemlerin "aslında zararsız olduğunu" göstermiyor. Bu dersin hiçbir kod bloğu bir
sarkan işaretçiyi dolaylamıyor, bırakılmış bir bloğa yazmıyor, bir bloğu iki kez bırakmıyor ya
da bir dizinin ötesine erişmiyor; hepsi bunları **önleyen** kalıbı koşturuyor. Bir programın
"çalışıyor görünmesi", içinde tanımsız davranış olmadığının kanıtı değildir — derleyici
sürümü, optimizasyon düzeyi ya da bir sonraki ayırma isteği değiştiğinde aynı kaynak farklı
davranabilir.

Bu, dersin ölçüm sınırını da belirliyor: burada koşturulan hiçbir program, tanımsız
davranışın "ne yaptığını" göstermiyor, çünkü öyle bir şey göstermek zaten kavramın
tanımıyla çelişir. Ders yalnız iki şeyi ölçebiliyor — sözleşmenin nerede kırıldığını
düzyazıyla anlatmayı ve kırılmanın yerine geçen tanımlı yolu koşturmayı. Aradaki fark, bir
laboratuvar deneyiyle bir tarif arasındaki farka benzer: deney sonucu gözlemlenebilir,
tarif yalnız izlenirse sonucu garanti eder — izlenmezse ne olacağı tarifin konusu değildir.

#### Özet

- Tanımsız davranış, derleyicinin **hiç girilmediğini varsayarak** optimizasyon yaptığı bir
  boşluktur; "çalışıyor görünmek" onun tanımlı olduğunu göstermez.
- Sarkan işaretçiyi okumak, bırakılmış belleği kullanmak, bir bloğu iki kez bırakmak ve dizinin
  sınırı dışına erişmek — dördü de tanımsızdır ve bu derste **hiçbiri koşturulmadı**.
- Her kaynağın tanımlı bir karşılığı vardır: geçerlilik bayrağı, tek-bırakma bayrağı ve
  indeks-uzunluk denetimi — hepsi programcının eklediği, dilin zorlamadığı satırlardır.
- Bu dersin ölçtüğü bellek **sahipsiz** sınıfındadır: sözleşme kırıldığında blok bırakılmakla
  yükümlü hiçbir tarafça artık tanımlı yoldan erişilemez.

#### Sonraki Adım

Sözleşmenin kırılması yalnız bellek erişiminde değil, bir işlevin **hata döndürdüğü** yolda da
görülür — orada da bir şeyin temizlenmesi gerekir, ama bu kez tanımsız davranışa girmeden.
Sonraki ders bu temizliği çağrılan sınıfının işlevi üzerinden ölçüyor: dönüş kodu, hata
numarası ve tek çıkış noktalı temizlik kalıpları.

### Hata Yönetimi Kalıpları

Source: https://academia.sh/tr/kurslar/c-bellek/hata-yonetimi-kaliplari

Bir işlev başarısız olduğunda, o ana kadar ayırdığı bloğu kimin bırakacağı derleyicinin denetlediği bir kural değil, işlevin kendi sözleşmesidir; ders bunu dönüş kodu, hata numarası ve tek çıkış noktalı temizlik kalıbıyla koşturarak ölçer.

Önceki ders sözleşmenin kırıldığı dört anı ele aldı ve hiçbirini koşturmadı. Bu ders
sözleşmenin **kırılmadığı**, ama işlevin **başarısız olduğu** bir anı ölçüyor: bir işlev bir
iş için bellek ayırmaya başlar, ortasında bir koşul tutmaz ve geri dönmesi gerekir. O ana
kadar ayrılmış blok artık kimsenin elinde değildir — kimin bırakacağı, kaynaktaki bir kuraldan
değil, o işlevin **kendi sözleşmesinden** çıkar.

Bu iki ders arasındaki fark ince ama bağlayıcıdır: tanımsız davranış sözleşmenin **kırıldığı**
andır, kısmi başarısızlık ise sözleşmenin **hâlâ ayakta olduğu** bir andır — işlev başarısız
oluyor, ama bu başarısızlık standartta tanımsız bırakılmış bir şey değil, işlevin kendisinin
öngördüğü bir yoldur. Aradaki fark ölçüme de yansıyor: önceki derste ölçülen şey "sözleşme
kırılınca hiçbir tarafın koruyamayacağı" bir bloktu; bu derste ölçülen şey, sözleşmeyi
**yazan tarafın**, kendi başarısızlığını nasıl temizlediğidir. Kalıp üç düzeyde ilerliyor: tek
kaynaklı hata yolu, çok kaynaklı hata yolu ve son olarak standart kütüphanenin kendi hata
bildirim yolu.

#### Hata Yolunda Temizlik

Ölçüm çekirdeğinde bu örneği zaten gördük: `kayit_olustur`, verilen ad `sinir` parametresine
sığmazsa, o ana kadar ayırdığı kaydı **kendisi** bırakır ve çağırana `NULL` döndürür.
Çağırana temizlenecek hiçbir şey kalmaz.

Bu kural yalnız tek bir bloğa uygulanıyor gibi görünse de, aslında bir işlevin dışarıya
verdiği **söz**dür: `NULL` dönen bir işlev, hiçbir şey ayırmamış gibi davranmayı taahhüt
ediyor demektir — çağıran `kayit_yok_et` çağırmak zorunda değildir, çünkü yok edecek bir şey
yoktur. Bu söz kaynak kodunda hiçbir anahtar sözcükle işaretlenmez; yalnız `sahip.h`'deki
yorumda yazılıdır ve `kayit_olustur`'un gövdesi o sözü tutar. Sözü tutmayan bir işlev —
örneğin ara bloğu ayırıp `NULL` döndükten sonra o bloğu bırakmayı unutan bir işlev — dilde
hiçbir uyarı üretmez; sonuç sessiz bir bellek sızıntısıdır ve yalnız sayaç farkıyla ortaya
çıkar.

Bu kesitin ölçümü, dersin tek `main`'ini içeren son kod bloğunda, öbür iki kalıpla birlikte
gösteriliyor. Ölçüm çekirdeğinin dört dosyası bu derste de aynen kullanılıyor:

```c
// 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
```

```c
// 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; }
```

```c
// sahip.h — kaydin ici gizli, sahiplik sozlesmesi yorumlarda yazili
#ifndef SAHIP_H
#define SAHIP_H

#include <stddef.h>

typedef struct Kayit Kayit;

/* Cagiran sahiplenir ve kayit_yok_et ile birakir. Ad sinira sigmazsa NULL
   doner; o ana kadar ayrilan bellegi islev kendisi birakir. */
Kayit *kayit_olustur(const char *ad, size_t sinir);

/* Kaydi ve kaydin icindeki ad tamponunu birlikte birakir. */
void kayit_yok_et(Kayit *k);

/* Kayit sahiplenir: donen isaretci kayit yok edilene dek gecerlidir,
   cagiran onu birakmaz. */
const char *kayit_ad(const Kayit *k);

/* Cagiran sahiplenir: donen kopya say_birak ile birakilir. */
char *kayit_ad_kopyasi(const Kayit *k);

#endif
```

```c
// sahip.c — sozlesmenin uygulandigi birim
#include <string.h>
#include "sahip.h"
#include "sayac.h"

struct Kayit {
    char *ad;
    size_t uzunluk;
};

Kayit *kayit_olustur(const char *ad, size_t sinir) {
    Kayit *k = say_ayir(sizeof *k);
    if (k == NULL) { return NULL; }

    k->uzunluk = strlen(ad);
    if (k->uzunluk >= sinir) {
        say_birak(k);
        return NULL;
    }

    k->ad = say_ayir(k->uzunluk + 1);
    if (k->ad == NULL) {
        say_birak(k);
        return NULL;
    }
    memcpy(k->ad, ad, k->uzunluk + 1);
    return k;
}

void kayit_yok_et(Kayit *k) {
    if (k == NULL) { return; }
    say_birak(k->ad);
    say_birak(k);
}

const char *kayit_ad(const Kayit *k) { return k->ad; }

char *kayit_ad_kopyasi(const Kayit *k) {
    char *kopya = say_ayir(k->uzunluk + 1);
    if (kopya != NULL) { memcpy(kopya, k->ad, k->uzunluk + 1); }
    return kopya;
}
```

#### Birden Çok Kaynağın Tek Çıkış Noktalı Temizliği

`kayit_olustur` tek bloğu kendi bırakıyor çünkü tek blok ayırıyor. Bir işlev birden çok bloğu
sırayla ayırıyorsa ve ortadaki biri geçersizse, önce ayrılmış olanları **geri sarmak**
gerekir. Bu kalıbın adı tek çıkış noktalı temizliktir: işlev tek bir `temizle:` etiketine
`goto` ile atlar ve o etiketten itibaren, o ana kadar ayrılmış olan her şeyi bırakır.

`goto` C'de genelde önerilmeyen bir yapıdır, çünkü kontrol akışını izlemeyi zorlaştırabilir.
Bu kalıp, o genel önerinin kabul edilmiş tek istisnasıdır: `goto` yalnız **ileri yönde**,
yalnız işlevin **sonundaki tek bir etikete**, yalnız temizlik amacıyla kullanılıyor. Alternatif
yazım — her başarısızlık noktasında o ana kadar ayrılmış her bloğu tek tek bırakan tekrarlı
kod — aynı işi yapar ama ayrılan blok sayısı arttıkça satır sayısı da katlanarak artar ve her
yeni kaynak eklendiğinde önceki tüm başarısızlık dallarının güncellenmesi gerekir. `goto
temizle` bunun yerine geri sarma mantığını **tek bir yerde** toplar; yeni bir kaynak
eklendiğinde yalnız yeni bir `if` ve yeni bir ayırma satırı eklenir, `temizle:` etiketinin
kendisi değişmez.

```c
// uclu.h — uc kaynakli ayirmanin bildirimi
#ifndef UCLU_H
#define UCLU_H

#include <stddef.h>

int uclu_ayir(size_t a, size_t b, size_t c, size_t sinir,
              int **p1, int **p2, int **p3);

#endif
```

```c
// uclu.c — goto temizle kalibiyla kismi basarisizlikta geri sarma
#include <stddef.h>
#include "uclu.h"
#include "sayac.h"

enum Durum { DURUM_TAMAM = 0, DURUM_HATA = 1 };

int uclu_ayir(size_t a, size_t b, size_t c, size_t sinir,
              int **p1, int **p2, int **p3) {
    int durum = DURUM_TAMAM;
    *p1 = NULL;
    *p2 = NULL;
    *p3 = NULL;

    if (a > sinir) {
        durum = DURUM_HATA;
        goto temizle;
    }
    *p1 = say_ayir(sizeof **p1);

    if (b > sinir) {
        durum = DURUM_HATA;
        goto temizle;
    }
    *p2 = say_ayir(sizeof **p2);

    if (c > sinir) {
        durum = DURUM_HATA;
        goto temizle;
    }
    *p3 = say_ayir(sizeof **p3);

    return durum;

temizle:
    say_birak(*p3);
    say_birak(*p2);
    say_birak(*p1);
    *p1 = *p2 = *p3 = NULL;
    return durum;
}
```

**SL5.** İlk çağrıda `b` değeri (**9**) sınırı (**5**) aştığı için `*p1` zaten ayrılmışken
`temizle:` etiketine atlanacak, o tek blok geri bırakılacak; ikinci çağrıda üçü de sınırın
altında kalacağı için hiçbir zaman `temizle:`e uğranmayacak, üç blok da çağırana geçecek ve
çağıran onları kendisi bırakacak. Aşağıdaki tek `main`, bu iki yolu da `kayit_olustur`'un
hata yolu ve `errno`'nun okunuşuyla birlikte tek koşumda ölçüyor.

`uclu_ayir`'ın `temizle:` etiketi yalnız **o ana kadar ayrılmış olanı** bırakır — `say_birak`
işlevi `NULL` işaretçiyle çağrıldığında hiçbir şey yapmadığı için (`sayac.c`'deki `if (blok !=
NULL)` denetimi), henüz ayrılmamış `*p2` ve `*p3` için `say_birak(*p2)` ve `say_birak(*p3)`
çağrıları güvenle boşa düşer. Bu, geri sarma kalıbının neden hep **aynı sırayla, hep aynı üç
çağrıyla** yazılabildiğini açıklıyor: kod, hangi bloğun gerçekten ayrıldığını ayrı ayrı
izlemek zorunda değil, çünkü `say_birak` zaten "ayrılmamışsa dokunma" garantisini taşıyor.
Garantiyi taşıyan `say_birak`'tır, `goto` değil — `goto` yalnız akışı tek bir noktaya toplar.

#### Dönüş Kodu ve Hata Numarası

`uclu_ayir` bir `int` döndürüyor ve bu değer yalnız **başarılı mı başarısız mı** sorusuna
yanıt veriyor; hangi kaynağın reddedildiğini söylemiyor — o bilgi isteniyorsa ayrı bir
parametreyle taşınmalıdır. Standart kütüphanenin kendi hata bildirimi ise `errno` üzerinden
işler: bir işlev başarısız olduğunda `errno`'yu **sıfırdan farklı** bir değere ayarlar, ama bu
değerin **kendisi** hedefe bağlıdır ve basılmaz; yalnız sıfır olup olmadığı okunur.

Dönüş kodu ve `errno` aynı sorunun iki farklı çözümüdür ama aynı katmanda çalışmazlar. Dönüş
kodu **o işlevin kendi tasarımıdır** — `uclu_ayir`'ın `int` dönüşü, o işlevi yazan kişinin
seçtiği bir sözleşmedir ve başka bir işlevin dönüş değeriyle hiçbir ortak kuralı paylaşmaz.
`errno` ise standart kütüphanenin **ortak** kanalıdır: `strtol` de, dosya işlemleri de, bellek
ayırma da aynı `errno` değişkenini kullanır — bu yüzden hangi çağrının hangi hatayı bıraktığını
ayırt etmek, her çağrıdan hemen önce sıfırlamayı zorunlu kılar.

```c
// olcum.c — hata yolu, goto temizle ve errno tek kosumda olculuyor
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include "sahip.h"
#include "sayac.h"
#include "uclu.h"

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

static void gozle(const char *ad, const char *sonuc) {
    printf("%-28s%s\n", ad, sonuc);
}

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

    printf("%-28s%s\n", "gozlem", "sonuc");

    /* --- hata yolunda temizlik: kayit_olustur kendi ayirdigini kendi birakir --- */
    int once = fark();
    Kayit *uzun = kayit_olustur("cok uzun bir kayit adi", 8);
    snprintf(tampon, sizeof tampon, "bos %d, sayac farki %d", uzun == NULL, fark() - once);
    gozle("hata yolunda temizlik", tampon);

    /* --- goto temizle: kismi basarisizlikta geri sarma --- */
    int *a = NULL, *b = NULL, *c = NULL;
    once = fark();
    int durum = uclu_ayir(2, 9, 2, 5, &a, &b, &c);
    snprintf(tampon, sizeof tampon, "durum %d, sayac farki %d", durum, fark() - once);
    gozle("ikinci kaynak reddedildi", tampon);

    once = fark();
    durum = uclu_ayir(1, 2, 3, 5, &a, &b, &c);
    say_birak(a);
    say_birak(b);
    say_birak(c);
    snprintf(tampon, sizeof tampon, "durum %d, sayac farki %d", durum, fark() - once);
    gozle("ucu de kabul edildi", tampon);

    /* --- errno: yalniz sifir olup olmadigi okunuyor --- */
    errno = 0;
    char *bitis = NULL;
    long tasan = strtol("99999999999999999999", &bitis, 10);
    (void)tasan;
    gozle("tasan sayi ayristirma", errno != 0 ? "hata numarasi sifir degil" : "hata numarasi sifir");

    errno = 0;
    long normal = strtol("42", &bitis, 10);
    (void)normal;
    gozle("normal sayi ayristirma", errno == 0 ? "hata numarasi sifir" : "hata numarasi sifir degil");

    return 0;
}
```

```text
gozlem                      sonuc
hata yolunda temizlik       bos 1, sayac farki 0
ikinci kaynak reddedildi    durum 1, sayac farki 0
ucu de kabul edildi         durum 0, sayac farki 0
tasan sayi ayristirma       hata numarasi sifir degil
normal sayi ayristirma      hata numarasi sifir
```

**SL6.** `kayit_olustur` içinde `k->uzunluk >= sinir` denetimi tutunca işlev kendi ayırdığı
`k`'yı `say_birak` ile bırakıp `NULL` döner; sayaç farkının **0** çıkması, ayrılan tek bloğun
aynı işlev içinde bırakıldığının kanıtıdır. Her iki `strtol` çağrısından önce `errno` açıkça
**sıfırlanıyor**: `errno` yalnız bir hata olduğunda ayarlanır, başarıda sıfırlanacağı garanti
değildir — önceki bir çağrıdan kalan değeri okumamak için denetimden önce sıfırlamak
zorunludur.

Tabloda görünen beş satırın hiçbiri birbirinin devamı değildir; her biri kendi `once`/`fark`
çiftiyle ya da kendi `errno` sıfırlamasıyla **bağımsız** başlıyor. Bu ayrım kasıtlı: iki
ölçümü art arda koşturup aradaki farkı tek bir sayaç okuyarak yorumlamak, hangi ölçümün hangi
değişikliğe yol açtığını karıştırabilir. `fark()` işlevinin her gözlemden önce çağrılması —
`once = fark();` satırı — bu karışmayı önlüyor: her satırın sayaç farkı yalnız **o satırın
kendi çağrısından** geliyor, öncekilerin birikimli etkisinden değil.

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

Kısmi başarısızlıkta ayrılmış olanı kimin bıraktığı her işlevde **ayrı ayrı** yazılmak
zorundadır. `kayit_olustur` kendi ayırdığını kendi bıraktı; `uclu_ayir` da öyle yaptı — ama
bu bir dil kuralı değil, o işlevlerin **kendi seçtiği** sözleşmedir. Başka bir işlev, kısmi
başarısızlıkta ayırdığını bırakmadan `NULL` dönebilir ve derleyici bunu engellemez; sözleşme
yalnız başlık dosyasındaki yorumda yazılıdır ve çağıran onu **okumak zorundadır**.

Bu, dersin ölçtüğü şeyin sınırını da çiziyor: `kayit_olustur` ve `uclu_ayir`'ın kendi
başarısızlık yollarını doğru temizlediğini gösterdik, ama bu ikisinin doğru yazılmış olması
başka bir işlevin de aynı disiplini izleyeceğinin kanıtı değildir. Her yeni işlev için aynı
soru yeniden sorulmalıdır: kısmi başarısızlıkta ne ayrıldı, kim bırakacak ve bu sözleşme
nerede yazılı? Ölçüm burada durur; sözleşmenin var olup olmadığını denetleyen bir araç değil,
sözleşmenin **bir örneğini** koşturan bir derstir bu.

Bu sınır, `errno` için de aynı şekilde geçerlidir: standart, bir işlevin başarısızlıkta
`errno`'yu ayarlayacağını garanti eder, ama **hangi işlevlerin** onu okuyup okumadan
üzerine yazabileceğini garanti etmez. Aradaki farkı gözlemlemenin tek yolu, her çağrıdan
hemen önce `errno`'yu sıfırlamak ve hemen sonra okumaktır — iki çağrı arasına başka bir
kütüphane işlevi girerse, o işlev başarılı olsa bile `errno`'yu değiştirmiş olabilir. Bu
derste ölçülen iki kalıp da (`goto temizle` ve `errno` sıfırlama) aynı ilkeye dayanıyor:
dil hiçbir temizliği ya da hiçbir sıfırlamayı **kendiliğinden** yapmaz, yapan taraf her
zaman kod satırının kendisidir.

#### Özet

- Bir işlev kısmi başarısızlıkta ayırdığı bloğu kendi bırakabilir (`kayit_olustur`) ya da
  bırakmadan geri dönebilir; hangisi olduğu işlevin **kendi sözleşmesindedir**.
- Tek çıkış noktalı temizlik (`goto temizle`) birden çok kaynağı sırayla geri sarmanın
  standart kalıbıdır ve iki farklı başarısızlık noktasında da sayaç farkını **0**'a getirdi.
- `errno` yalnız **sıfır olup olmadığıyla** okunur; numara değeri ve ileti metni hiçbir
  gözlemde basılmadı.
- Bu dersin ölçtüğü bellek **çağrılan** sınıfındadır: hata yolunda temizliği modülün kendisi
  üstlendi, çağırana kalan bir şey olmadı.
- Üç kalıbın da ortak noktası, dilin hiçbirini **kendiliğinden** sağlamamasıdır — `goto
  temizle` etiketini yazan, `say_birak`'ın `NULL`'da boşa düştüğünü garanti eden ve `errno`'yu
  her çağrıdan önce sıfırlayan, hep programcının kendi satırlarıdır.

#### Sonraki Adım

`kayit_olustur` hata yolunda temizlik yaparken bir şeyi de reddetti: verilen adın sınıra
sığmaması. O sınır — `sinir` parametresi — kaynaktan gelmiyor, **çağıran tarafından**
veriliyor. Sonraki ders bu parametreyi merkeze alıyor: uzunluğu çağıranın vermesi, bir
arabellek taşmasını nasıl önlüyor.

### Sınır Denetimi

Source: https://academia.sh/tr/kurslar/c-bellek/sinir-denetimi

kayit_olustur'un sinir parametresi uzunluğu kaynaktan değil, çağırandan alır; ders bu parametrenin bir arabellek taşmasını nasıl önlediğini, snprintf'in kesme bildirimini ve uzunluğun kimin sorumluluğunda olduğunu koşturarak gösterir.

Önceki ders `kayit_olustur`'un hata yolunda temizliğini ölçtü; o hata yolu bir tek koşuldan
doğuyordu: verilen adın uzunluğu `sinir` parametresini aşıyordu. Bu ders o parametrenin
kendisini merkeze alıyor. `sinir`, dilin ya da `sahip.c` biriminin bir sabiti değildir —
**çağıranın** her çağrıda verdiği bir değerdir ve bir arabellek taşmasını önleyen şey tam
olarak budur.

Önceki iki ders "sahipsiz" ve "çağrılan" sınıflarını ölçtü; bu ders üçüncü sınıfa geçiyor:
**çağıran**. Aradaki fark şu soruda somutlaşıyor — bir arabellek taşmasını kim önler? Önceki
derste yanıt "çağrılan işlevin kendisi"ydi: `kayit_olustur` kendi ayırdığı bloğu kendi
sınırlıyordu. Bu derste ölçülen iki işlev de (`kayit_olustur`'un `sinir`'i ve
`sinirli_kopyala`'nın `hedef_boyut`'u) sınırı **kendileri belirlemiyor** — sınırı çağıran
belirliyor ve işlev yalnız o sınıra **uyuyor**. Sınırın doğru olup olmadığı, işlevin değil,
çağıranın sorumluluğudur.

#### Uzunluğu Çağıran Verir

`kayit_olustur(const char *ad, size_t sinir)` imzasına bakıldığında `sinir`'in nereden
geldiği görünmez; imza yalnız bir `size_t` alır. Ama sözleşme başlık dosyasındaki yorumda
yazılıdır: işlev, `ad`'in uzunluğunu `sinir`'e göre denetler ve sığmazsa `NULL` döner. Bu
denetim dilin bir parçası değildir — `strlen` sınır tanımaz, `memcpy` sınır tanımaz; sınırı
tanıyan tek şey `kayit_olustur`'un kendi gövdesindeki karşılaştırmadır.

Bu noktada dikkat edilmesi gereken şey, `sinir`'in **nereden** geldiğidir. İmzada `size_t
sinir` yazması, değerin sabit ya da güvenli olduğu anlamına gelmez — çağıran onu bir
sabitten, bir kullanıcı girdisinden ya da başka bir hesaplamadan türetebilir. `kayit_olustur`
kendisine verilen `sinir`'in **doğru** olup olmadığını denetlemez, yalnız `ad`'in o `sinir`'e
sığıp sığmadığını denetler. Sınırın kendisi yanlışsa — örneğin gerçek hedef kapasitesinden
büyükse — işlev hâlâ doğru çalışır ama denetimin koruduğu şey artık gerçek hedef değil,
çağıranın yanlış bildirdiği sayıdır. Bu ayrım dersin sonundaki "Sınırlayıcı Ölçüm" bölümünde
yeniden ele alınıyor.

```c
// 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
```

```c
// 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; }
```

```c
// sahip.h — kaydin ici gizli, sahiplik sozlesmesi yorumlarda yazili
#ifndef SAHIP_H
#define SAHIP_H

#include <stddef.h>

typedef struct Kayit Kayit;

/* Cagiran sahiplenir ve kayit_yok_et ile birakir. Ad sinira sigmazsa NULL
   doner; o ana kadar ayrilan bellegi islev kendisi birakir. */
Kayit *kayit_olustur(const char *ad, size_t sinir);

/* Kaydi ve kaydin icindeki ad tamponunu birlikte birakir. */
void kayit_yok_et(Kayit *k);

/* Kayit sahiplenir: donen isaretci kayit yok edilene dek gecerlidir,
   cagiran onu birakmaz. */
const char *kayit_ad(const Kayit *k);

/* Cagiran sahiplenir: donen kopya say_birak ile birakilir. */
char *kayit_ad_kopyasi(const Kayit *k);

#endif
```

```c
// sahip.c — sozlesmenin uygulandigi birim
#include <string.h>
#include "sahip.h"
#include "sayac.h"

struct Kayit {
    char *ad;
    size_t uzunluk;
};

Kayit *kayit_olustur(const char *ad, size_t sinir) {
    Kayit *k = say_ayir(sizeof *k);
    if (k == NULL) { return NULL; }

    k->uzunluk = strlen(ad);
    if (k->uzunluk >= sinir) {
        say_birak(k);
        return NULL;
    }

    k->ad = say_ayir(k->uzunluk + 1);
    if (k->ad == NULL) {
        say_birak(k);
        return NULL;
    }
    memcpy(k->ad, ad, k->uzunluk + 1);
    return k;
}

void kayit_yok_et(Kayit *k) {
    if (k == NULL) { return; }
    say_birak(k->ad);
    say_birak(k);
}

const char *kayit_ad(const Kayit *k) { return k->ad; }

char *kayit_ad_kopyasi(const Kayit *k) {
    char *kopya = say_ayir(k->uzunluk + 1);
    if (kopya != NULL) { memcpy(kopya, k->ad, k->uzunluk + 1); }
    return kopya;
}
```

**SL7.** `kayit_olustur` aynı `ad` ile iki farklı `sinir` değeriyle çağrıldığında iki farklı
sonuç verebilir — sonucu belirleyen `ad` değil, çağıranın verdiği `sinir`'dir. Aşağıdaki ölçüm
bunu tam olarak bu şekilde gösteriyor: aynı ad, biri dar biri geniş iki sınırla.

Bu gözlem, önceki dersteki `kayit_olustur` ölçümünden kasıtlı olarak farklı bir açıdan
bakıyor. Önceki ders `sinir`'i **sabit tuttu** ve `ad`'in uzunluğunu değiştirerek hata yolunu
tetikledi; bu ders tam tersini yapıyor — `ad`'i **sabit tutuyor** ve `sinir`'i değiştiriyor.
İkisi de aynı karşılaştırmayı (`k->uzunluk >= sinir`) tetikliyor, ama hangi tarafın
değiştiğini göstermek, sözleşmenin hangi parametreye ait olduğunu netleştiriyor: karşılaştırma
her iki değişkene de bakıyor, ama bu derste sorulan soru özellikle "sınırı kim veriyor"
olduğu için, sabit tutulması gereken taraf `ad`, değiştirilmesi gereken taraf `sinir`'dir.

#### Sabit Boyutlu Arabelleğe Kesmeli Kopyalama

`kayit_olustur` kaynağı ayırıp kopyalıyor; ama bazen hedef **sabit boyutlu** bir yerel
arabellektir ve kaynağın uzunluğu önceden bilinmez. Bu durumda `strcpy` kullanmak
arabellek taşmasına açık kapı bırakır: `strcpy` hedefin boyutunu hiç bilmez, kaynağı
olduğu gibi kopyalar. `snprintf` ise hedefin boyutunu bir parametre olarak alır ve asla
o boyutu aşmaz; kestiğinde de bunu dönüş değeriyle **bildirir** — dönüş değeri, kesme
olmasaydı yazılacak olan toplam uzunluktur.

Bu iki kalıp arasındaki fark, önceki bölümdeki `kayit_olustur`'la karşılaştırıldığında daha
net görülüyor. `kayit_olustur` sınırı aştığında hiçbir şey yazmadan `NULL` döner — kısmi bir
yazma söz konusu değildir. `sinirli_kopyala` ise farklı davranır: hedefe **sığdığı kadarını**
yazar ve gerçek uzunluğu ayrıca bildirir. İkisi de arabellek taşmasını önlüyor, ama biri
"ya tam sığar ya da hiç yazmam" derken öbürü "sığdığım kadarını yazarım, ama sana kaç
karakterin sığmadığını da söylerim" diyor. Hangi kalıbın uygun olduğu, hedefin ne için
kullanılacağına bağlıdır: bir kayıt adı gibi bütünlüğü bozulmaması gereken bir veri için
ilki, bir günlük satırı gibi kesilmesi kabul edilebilir bir metin için ikincisi tercih
edilir.

```c
// kesme.h — sinirli hedefe kesmeli kopyalamanin bildirimi
#ifndef KESME_H
#define KESME_H

#include <stddef.h>

/* Hedefin boyutu hedef_boyut'tur. Donen deger, kesme olmasaydi yazilacak
   toplam uzunluktur; bu deger hedef_boyut'tan buyukse kesme olmustur. */
size_t sinirli_kopyala(char *hedef, size_t hedef_boyut, const char *kaynak);

#endif
```

```c
// kesme.c — snprintf ile sinir asilmadan kopyalama
#include <stdio.h>
#include "kesme.h"

size_t sinirli_kopyala(char *hedef, size_t hedef_boyut, const char *kaynak) {
    int yazilacak = snprintf(hedef, hedef_boyut, "%s", kaynak);
    if (yazilacak < 0) {
        return 0;
    }
    return (size_t)yazilacak;
}
```

**SL8.** `sinirli_kopyala`'nın döndürdüğü değer ile `hedef_boyut - 1` karşılaştırıldığında
kesilip kesilmediği anlaşılır: dönüş değeri hedefe **sığmayan** uzunluktur, hedefe fiilen
yazılan uzunluk değildir. Bu iki sayı aynı olabilir (kesme yoksa) ya da farklı olabilir
(kesme varsa) — ders aşağıdaki ölçümde ikisini de gösteriyor. Karşılaştırmanın `hedef_boyut -
1` ile yapılması, `snprintf`'in hedefe her zaman bir sonlandırıcı sıfır bayt eklediğinin
sonucudur: `hedef_boyut` baytlık bir alanda en fazla `hedef_boyut - 1` karakter ve bir
sonlandırıcı sığar.

#### İki Sınırla Tek Ölçüm

Aşağıdaki tek `main`, aynı iki kaynağı iki farklı sınırla ölçüyor: `kayit_olustur` için dar
ve geniş bir `sinir`, `sinirli_kopyala` için küçük ve yeterli bir `hedef_boyut`.

```c
// olcum.c — cagiranin verdigi sinirin iki farkli degerle olculmesi
#include <stdio.h>
#include "sahip.h"
#include "sayac.h"
#include "kesme.h"

static void gozle(const char *ad, const char *sonuc) {
    printf("%-28s%s\n", ad, sonuc);
}

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

    printf("%-28s%s\n", "gozlem", "sonuc");

    /* --- kayit_olustur: ayni ad, iki farkli sinir --- */
    Kayit *dar = kayit_olustur("saglamlik", 6);
    snprintf(tampon, sizeof tampon, "kayit %s", dar == NULL ? "reddedildi" : "kabul edildi");
    gozle("dar sinir (6)", tampon);

    Kayit *genis = kayit_olustur("saglamlik", 32);
    snprintf(tampon, sizeof tampon, "kayit %s", genis == NULL ? "reddedildi" : "kabul edildi");
    gozle("genis sinir (32)", tampon);
    kayit_yok_et(genis);

    /* --- sinirli_kopyala: ayni kaynak, iki farkli hedef boyutu --- */
    char kucuk[8];
    size_t gerekli_kucuk = sinirli_kopyala(kucuk, sizeof kucuk, "saglamlik dersi");
    snprintf(tampon, sizeof tampon, "gerekli %zu, sigan %zu, %s",
             gerekli_kucuk, sizeof kucuk - 1,
             gerekli_kucuk >= sizeof kucuk ? "kesildi" : "kesilmedi");
    gozle("kucuk hedef (8)", tampon);

    char yeterli[32];
    size_t gerekli_yeterli = sinirli_kopyala(yeterli, sizeof yeterli, "saglamlik dersi");
    snprintf(tampon, sizeof tampon, "gerekli %zu, sigan %zu, %s",
             gerekli_yeterli, sizeof yeterli - 1,
             gerekli_yeterli >= sizeof yeterli ? "kesildi" : "kesilmedi");
    gozle("yeterli hedef (32)", tampon);

    return 0;
}
```

```text
gozlem                      sonuc
dar sinir (6)               kayit reddedildi
genis sinir (32)            kayit kabul edildi
kucuk hedef (8)             gerekli 15, sigan 7, kesildi
yeterli hedef (32)          gerekli 15, sigan 31, kesilmedi
```

**SL9.** `"saglamlik"` dokuz karakterdir; dar sınır (**6**) onu reddediyor, geniş sınır
(**32**) aynı adı kabul ediyor — aradaki fark kaynakta değil, çağıranın verdiği sayıdadır.
`sinirli_kopyala`'da da aynı kaynak (`"saglamlik dersi"`, on beş karakter) iki farklı hedef
boyutuyla ölçülüyor: sekiz baytlık hedef yedi karaktere sığdığı için kesiliyor, otuz iki
baytlık hedef on beş karaktere rahatça sığdığı için kesilmiyor — `gerekli` değeri her iki
çağrıda da **aynı** kalıyor, değişen yalnız hedefin kapasitesi.

Dört gözlemin ikisi `kayit_olustur` üzerinden, ikisi `sinirli_kopyala` üzerinden geliyor, ama
dördü de aynı yapıyı paylaşıyor: **aynı kaynak, iki farklı sınır**. Bu tekrar kasıtlı — dersin
tek bir kanıtı yok, iki bağımsız işlevde aynı ilkeyi gösteren iki kanıtı var. `kayit_olustur`
sınırı aşınca tamamen reddediyor; `sinirli_kopyala` sınırı aşınca kısmen kabul edip durumu
bildiriyor. İki farklı tepki, aynı kaynağın (çağıranın verdiği sınır) iki farklı sonucu
nasıl belirleyebileceğini gösteriyor. `main` içindeki `once`/`fark` düzenine burada ihtiyaç
yok, çünkü hiçbir gözlem bir öncekinin sayaç durumuna bağlı değil — her ikisi de kendi
girdisiyle, kendi sınırıyla bağımsız ölçülüyor; `dar` ve `genis` işaretçilerinden yalnız
`genis` bırakılıyor çünkü `dar` zaten `NULL`, `kayit_yok_et(NULL)` de güvenle hiçbir şey
yapmıyor.

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

`sinir` ve `hedef_boyut` parametrelerinin **doğru** değerle çağrılması derleyicinin
denetlediği bir şey değildir. `kayit_olustur(ad, 1000000)` gibi gerçek boyuttan kopuk, aşırı
büyük bir sınır vermek de derlenir; bu durumda işlev kendi içindeki denetimi hâlâ doğru
uygular, ama denetim artık **anlamsız** hale gelir çünkü çağıran gerçek hedefin boyutunu
yanlış bildirmiştir. Sınır denetimi, sınırın kendisi yanlışsa hiçbir şeyi kurtarmaz.

Aynı sınır, `sinirli_kopyala`'nın `hedef_boyut` parametresi için de geçerlidir. `sizeof kucuk`
ifadesi burada doğru çalışıyor çünkü `kucuk` gerçek bir dizi değişkenidir ve derleyici onun
boyutunu bilir; ama bir işaretçi parametresine `sizeof` uygulansaydı — örneğin `kucuk` bir
işlev parametresi olarak `char *kucuk` şeklinde alınsaydı — `sizeof kucuk` dizinin boyutunu
değil, işaretçinin kendi boyutunu verirdi ve bu da sessizce yanlış bir `hedef_boyut`
üretirdi. Bu derste `sizeof kucuk` ve `sizeof yeterli` doğrudan `main` içindeki dizi
tanımlarının üzerinde çağrıldığı için bu tuzağa girilmiyor, ama kalıbı başka bir işleve
taşırken bu ayrım gözden kaçırılabilir; `hedef_boyut`'u her zaman **dizinin tanımlandığı
kapsamda** hesaplamak, işaretçiye dönüştükten sonra hesaplamamak gerekir.

Sınırın doğruluğu bir kez saptandıktan sonra bile, sınırın **kalıcılığı** ayrı bir sorudur.
`kayit_olustur`'a verilen `sinir` yalnız o tek çağrı için geçerlidir; aynı `Kayit` daha sonra
başka bir işleve geçirilirken orijinal `sinir` değeri taşınmaz — yalnız `kayit_ad`'in
döndürdüğü işaretçi ve `kayit_ad_kopyasi`'nin ürettiği kopya taşınır. Bu, çağıranın
sorumluluğunun tek bir çağrıyla bitmediğini gösteriyor: sınırı ilk veren çağıran, o sınırın
hangi varsayımla (örneğin "bu ad en fazla otuz bir karakter olacak") verildiğini de
belgelemek zorundadır, çünkü sonraki kod o varsayımı kaynaktan okuyarak değil, yalnız
belgeden okuyarak öğrenebilir.

#### Özet

- `kayit_olustur`'un `sinir` parametresi kaynaktan değil **çağırandan** gelir; aynı ad, iki
  farklı sınırla iki farklı sonuç verdi.
- `snprintf`, hedefin boyutunu parametre olarak alıp asla aşmayan ve kesildiğinde bunu dönüş
  değeriyle bildiren bir kopyalama sağladı; `strcpy` bu bildirimi hiç yapmaz.
- İki ayrı hedef boyutuyla aynı kaynak ölçüldüğünde `gerekli` uzunluk sabit kaldı, yalnız
  kesilip kesilmediği değişti — sınırın doğruluğu hedefin gerçek kapasitesine bağlıdır.
- Bu dersin ölçtüğü bellek **çağıran** sınıfındadır: hedefin boyutunu bilen ve bildiren taraf
  koddaki `sinir` ya da `hedef_boyut` parametresini veren çağrının kendisidir.
- Bu üç ders, kursun "kim sahipleniyor" ekseninde üç ayrı sınıfı sırayla ölçtü: önceki iki
  ders sahipsiz ve çağrılan sınıflarını sözleşmenin kırıldığı ve işlevin başarısız olduğu
  anlarla, bu ders çağıran sınıfını sınırın kaynakta değil çağıranda yazılı olduğunu
  göstererek ölçtü. Üç dersin ortak paydası, hiçbirinde koruyucu tarafın dil olmamasıdır —
  koruma her seferinde ayrı bir tarafın kendi eklediği bir satırdan geliyor.

#### Sonraki Adım

Bu üç ders sözleşmenin kırıldığı, işlevin başarısız olduğu ve çağıranın sınır verdiği anları
ölçtü — üçü de aynı platformda, aynı derleyicide koşturuldu. Bu ortak varsayım şimdiye kadar
hiç sorgulanmadı: her ölçüm, aynı `cc`, aynı bayraklar, aynı işletim sistemi altında aynı
sonucu üretti. Ama tanımlı davranışın kendisi bile her yerde birebir aynı değildir — bir
kısmı standarda göre tanımlıyken, hangi **değeri** alacağı uygulamaya bağlı kalabilir; bu
derste ölçülen `sinirli_kopyala`'nın döndürdüğü uzunluk türü olan `size_t`'in kendi genişliği
bile platformdan platforma değişebilir. Sonraki ders bu sözleşmenin **platformlar arasında**
ne kadar değiştiğini ele alıyor: hangi davranış tanımlı kalır, hangisi uygulamaya bağlıdır ve
kod bunu nasıl taşınabilir tutar. Bu dersteki üç örnek de bu yüzden yalnız bir platformun
kanıtıdır — sonucun kendisi değişmeden kalsa bile, hangi varsayımların o sonucu taşıdığı
henüz açık yazılmadı. O yazım işi, ders kodunun bir satırını bile değiştirmeden, yalnız
derleme ortamını değiştirerek başlıyor.

### Taşınabilirlik

Source: https://academia.sh/tr/kurslar/c-bellek/tasinabilirlik

Ortak tanımın bilerek basmadığı üç değer — adresler, char dışındaki tip boyutları, malloc(0) dönüşü — hedefe bağlıdır; bu ders bir değeri basmak yerine standardın garanti ettiği bir karşılaştırmayı basarak hedefe bağlı davranışı yalıtıyor ve kursun on beş dersini kapanış tablosunda topluyor.

Önceki ders `sinir` parametresinin kaynakta değil çağıranda yazılı olduğunu ölçtü.
Bu ders kursun kendisini kapatıyor ve geriye tek bir soru bırakıyor: ölçüm çekirdeği baştan beri neyi **basmadı**. `sahiplik.c` on üç gözlemi bastı, ama üç şeyi hiç
basmadı — adresler, `sizeof(char)` dışındaki tip boyutları ve `malloc(0)`'ın boş dönüp
dönmediği. Üçü de aynı nedenle dışarıda bırakıldı: üçü de **hedefe bağlıdır**, yani aynı
kaynak metin iki farklı hedefte iki farklı sayı üretebilir.

Bu ders o üç değeri basmaya çalışmıyor. Onun yerine standardın **garanti ettiği** bir
karşılaştırmayı basıyor. Aradaki fark şu: `sizeof(int)`'i basmak bir sayı verir ve o sayı
hedefe göre değişir; `sizeof(char) == 1` sorusunu sormak da bir sonuç verir, ama bu sonuç
her hedefte **aynıdır**, çünkü standardın kendisi bunu garanti eder. Ölçülen şey aynı
soruya iki farklı yaklaşım değil — biri hedefe bağlı bir değeri açığa çıkarır, öteki
standardın hedeften bağımsız bıraktığı mantıksal sonucu açığa çıkarır. Bu dersin sahiplik
sınıfı **dil**dir: garantiyi veren taraf çağıran, çağrılan ya da sahipsiz kalan bir blok
değil, dilin standardının kendisidir.

#### Üç Karşılaştırma, Tek Yalıtım Tekniği

Ölçüm çekirdeğinin sayaç birimi bu derste de kullanılıyor; kesiti aşağıda, yeniden
yazılmadan.

```c
// 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
```

```c
// 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;
}
```

**SL10.** `sayac.c` bu derste de aynı davranışı sürdürüyor: `say_ayir` yalnız dönüş
`NULL` değilse sayacı artırıyor, `say_birak` yalnız `NULL` değilse sayacı artırıp yine de
`free`'yi çağırıyor. Bu iki satır bu dersin son gözlemi için önceden kuruluyor — `boyut`
sıfır olduğunda `malloc`'ın ne döndüreceği hedefe bağlı olsa da, `sayac.c`'nin bu iki
satırının davranışı **değişmiyor**.

Yeni dosya üç karşılaştırmayı ve sıfır boyutlu ayırmayı bir arada ölçüyor. Hiçbir satır bir
boyutu, bir sınır değerini ya da bir adresi basmıyor; her satır yalnız bir karşılaştırmanın
sonucunu basıyor.

```c
// tasinirlik.c — bir degeri basmak yerine standardin garantisini basar
#include <stdint.h>
#include <limits.h>
#include <stdio.h>
#include "sayac.h"

static void gozle(const char *ad, const char *sonuc) {
    printf("%-34s%s\n", ad, sonuc);
}

int main(void) {
    printf("%-34s%s\n", "gozlem", "sonuc");

    gozle("sabit genislikli tam sayi", sizeof(int32_t) * CHAR_BIT == 32 ? "garanti tutuyor" : "garanti tutmuyor");
    gozle("char boyutu", sizeof(char) == 1 ? "garanti tutuyor" : "garanti tutmuyor");
    gozle("int alt siniri", INT_MAX >= 32767 ? "garanti tutuyor" : "garanti tutmuyor");

    int birinci = 7;
    int ikinci = 7;
    int *aynisi = &birinci;
    int *tekrar = &birinci;
    int *ayrisi = &ikinci;
    gozle("ayni degiskenin iki adresi", aynisi == tekrar ? "esit" : "esit degil");
    gozle("iki ayri degiskenin adresi", aynisi == ayrisi ? "esit" : "esit degil");

    int once = ayrilan_sayisi() - birakilan_sayisi();
    void *sifir_boyut = say_ayir(0);
    say_birak(sifir_boyut);
    int sonra = ayrilan_sayisi() - birakilan_sayisi();
    gozle("sifir boyutlu ayirma sozlesmesi", sonra - once == 0 ? "sozlesme saglandi" : "sozlesme bozuldu");

    return 0;
}
```

```
gozlem                            sonuc
sabit genislikli tam sayi         garanti tutuyor
char boyutu                       garanti tutuyor
int alt siniri                    garanti tutuyor
ayni degiskenin iki adresi        esit
iki ayri degiskenin adresi        esit degil
sifir boyutlu ayirma sozlesmesi   sozlesme saglandi
```

**SL11.** İlk üç satır aynı kalıbı izliyor: bir tip boyutunu ya da bir sınır değerini
doğrudan basmak yerine, standardın zaten garanti ettiği bir alt sınırla karşılaştırıyor.
`int32_t` var olduğunda tam olarak otuz iki bit genişliğindedir — bu, tanımın kendisinden
gelir, hedeften değil. Karşılaştırmanın bayt sayısıyla değil bit sayısıyla kurulmasının nedeni
de budur: bir bayttaki bit sayısı (`CHAR_BIT`) standartta yalnız en az sekiz olarak garanti
edilir, bu yüzden `sizeof(int32_t)` hedeften hedefe değişebilir, `sizeof(int32_t) * CHAR_BIT`
ise değişmez. `sizeof(char)` her zaman birdir — bu da tanımdan gelir; kursun başından beri
`sizeof(char)` dışındaki hiçbir boyutun basılmamasının nedeni tam olarak budur. `INT_MAX`
standart tarafından en az `32767` olacak şekilde garanti edilir; gerçek değeri hedefe göre
daha büyük olabilir, ama bu alt sınırın **tutup tutmadığı** her hedefte aynı sonucu verir.

**SL12.** Dördüncü ve beşinci satır adresleri basmadan işaretçi eşitliğini ölçüyor. `aynisi`
ile `tekrar` aynı değişkenin adresini tutuyor; ikisinin eşitliği bir dil kuralından gelir —
aynı ifade (`&birinci`) her değerlendirildiğinde aynı sonucu verir. `aynisi` ile `ayrisi` ise
aynı anda yaşayan iki ayrı otomatik nesnenin adreslerini tutuyor; standart bu iki nesnenin
çakışmayacağını garanti eder, bu yüzden eşitsizlik de bir uygulama tesadüfü değil, bir dil
garantisidir. İki satır da adresin **kendisini** hiç göstermeden bu iki garantiyi ölçüyor.

**SL13.** Son satır ortak tanımın üçüncü basılmayanını ele alıyor: `malloc(0)`'ın boş
döndürüp döndürmediği hedefe bağlıdır ve bu ders o soruyu **yanıtlamıyor**. Bunun yerine
`sayac.c`'nin iki satırının bu belirsizlikten etkilenip etkilenmediğini ölçüyor. `say_ayir(0)`
`NULL` dönerse sayaç hiç artmaz ve `say_birak(NULL)` da hiç artırmaz — fark sıfır kalır.
`say_ayir(0)` geçerli bir işaretçi dönerse sayaç bir artar ve `say_birak` onu bırakırken
sayaç da bir artar — fark yine sıfır kalır. `malloc(0)`'ın hangi dalı izlediği hiçbir zaman
görünmüyor; görünen tek şey, sözleşmenin **her iki dalda da** aynı kaldığıdır.

#### Sabit Genişlikli Tiplerin Kendi Sınırı

`<stdint.h>`'ın sunduğu yalıtım kendi başına sınırsız değil. Standart, `int32_t` gibi tam
genişlikli tipleri yalnız hedef platformda öyle bir tip **varsa** tanımlamayı zorunlu kılar;
yokluğunda bu tipler hiç tanımlanmaz ve onları kullanan bir kaynak dosya derlenmez. Standart,
her hedefte bulunmasını zorunlu kıldığı tipleri ayrı adlarla verir — `int_least32_t` en az
otuz iki bit genişliğinde olmayı garanti eder, `int32_t` ise tam olarak otuz iki bit
genişliğinde olmayı, ama yalnız var olduğunda.

**SL14.** Bu derste `int32_t`'nin varlığını sınamak yerine doğrudan kullanılması, dersin
kendisinin bir varsayımı: dersin derlendiği ortamda bu tip **var**. Yalıtım tekniğinin
kendisi de böylece bir varsayıma yaslanıyor — genişliğin değil, tipin **varlığının**
varsayımına. Tam genişliğin gerekmediği bir yerde taşınabilir bir kaynak `int_least32_t` ya da
`int_fast32_t` kullanır; bu ders `int32_t`'yi yalnız örnek için seçti.

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

Bir değeri basmak yerine bir garantiyi basmak, hedefe bağlı olanı **ortadan kaldırmaz** —
yalnız onu gözlemin dışına taşır. `sizeof(int32_t) * CHAR_BIT == 32` ifadesi her hedefte aynı
sonucu verir, ama bunu yalnızca `int32_t` **var olduğunda** yapar; standardın kendisi bu tipin
var olmasını zorunlu kılmaz. Aynı biçimde `INT_MAX >= 32767` her hedefte tutar, ama gerçek
`INT_MAX` değeri hedeften hedefe değişebilir — ders bu değeri hiç basmayarak taşınırlığı
korur, ama sınamak istediği asıl soruyu (`INT_MAX` tam olarak kaçtır) yanıtsız bırakır. Bir
karşılaştırma bir değerden daha taşınabilirdir, ama daha az bilgi taşır; taşınabilirlik ile
bilgi arasındaki bu değiş tokuş, dilin garantisinin **nerede bittiğini** işaretler. Ölçüm
çekirdeğinin dört sınıfından hiçbiri bu değiş tokuşu ortadan kaldırmaz — dil sınıfı da dahil,
çünkü garantiyi veren dil, garantinin kapsamını da kendisi çizer.

#### Özet

- Bu dersin ölçtüğü bellek sınıfı **dil**dir: garantiyi veren taraf çağıran, çağrılan ya da
  sahipsiz kalan bir blok değil, dilin standardının kendisidir.
- Ortak tanımın bilerek basmadığı üç değer — adresler, `sizeof(char)` dışındaki tip
  boyutları, `malloc(0)`'ın dönüşü — üçü de hedefe bağlıdır ve bu derste de basılmadı.
- Bir değer yerine standardın garanti ettiği bir karşılaştırma basıldığında sonuç her
  hedefte aynı kalır; `sizeof(int32_t) * CHAR_BIT == 32`, `sizeof(char) == 1` ve
  `INT_MAX >= 32767` üçü de bu kalıbı izledi.
- Aynı anda yaşayan iki otomatik nesnenin adresleri çakışmaz; bu eşitsizlik adresin kendisi
  hiç basılmadan, yalnız `==` karşılaştırmasıyla ölçüldü.
- `malloc(0)`'ın boş dönüp dönmediği hedefe bağlı kalsa da, `sayac.c`'nin ayırma-bırakma
  sözleşmesi bu belirsizlikten etkilenmedi: fark her iki dalda da sıfır kaldı.
- Bir garantiyi basmak hedefe bağlı olanı ortadan kaldırmaz, yalnız gözlemin dışına taşır —
  `int32_t`'nin kendisi de var olmayabilecek bir tiptir.

#### Kurs Kapanışı

Kurs boyunca tek bir soru soruldu: bu belleği kim sahipleniyor. On dört ders bu soruyu dört
sınıfla yanıtladı — dil, çağıran, çağrılan, sahipsiz — ve bu ders, aynı sorunun dilin
kendisine sorulduğunda ne söylediğini gösterdi: standardın garanti ettiği yerde dil
sahiplenir, garantinin bittiği yerde gözlem hedefe bağlı kalır ve basılmaz.

| Ders | Ölçülen bellek | Sahibi | Sınırlayıcı ölçüm |
|---|---|---|---|
| İşaretçi Temelleri | Yerel değişken, statik alan, dizgi değişmezi, yerel dizi | dil | Bir işaretçinin tipi kimin bırakacağını söylemez |
| İşaretçi Aritmetiği | Dizi içi ilerleme ve eleman farkı | dil | Sınırın bir ötesini göstermek geçerlidir, dolaylamak tanımsızdır |
| Diziler ve İşaretçiler | Dinamik olarak ayrılmış dizi | çağıran | Bozunma yalnız bir düzeyde çalışır, eleman sayısını taşımaz |
| Boş ve Sarkan İşaretçiler | Bırakılmış bloğu gösteren işaretçi | sahipsiz | NULL atamak yalnız o işaretçiyi denetlenebilir kılar, aynı bloğu gösteren başka bir işaretçiyi etkilemez |
| `void` ve Opak İşaretçiler | Opak kayıt (`Kayit`) | çağrılan | Opaklık yapının içini gizler, sahipliği gizlemez: bırakma yükümlülüğü yalnız yorumda yazılıdır |
| Fonksiyon İşaretçileri | Geri çağrıya taşınan bağlam | çağıran | Bir imza davranışı garanti etmez, yalnız tip uyumunu denetler |
| Bellek Ayırma İşlevleri | `malloc`/`calloc`/`realloc` ile ayrılan blok | çağıran | `malloc(0)` ve `realloc(blok, 0)` dönüşü hedefe bağlıdır, hiç basılmaz |
| Serbest Bırakma Disiplini | Ödünç verilen ad ile döndürülen kopya | çağrılan | Sahiplik imzadan değil, başlık dosyasındaki yorumdan anlaşılır |
| Bellek Sızıntıları | Sahiplenilmeyen iki blok | sahipsiz | Sızıntı çökme ya da uyarı üretmez, yalnız sayaç farkıyla görünür |
| Bellek Hata Ayıklama Araçları | Dengeli ve dengesiz ayırma-bırakma çifti | sahipsiz | Bir araç yalnız eşleşmeyen çağrıyı sayar, sözleşme ihlalini okumaz |
| Bellek Düzeni | Dört bellek bölgesindeki değişken | dil | Bölge sınırları ve hizalama basılmaz; öbek dışında sahiplik bölgeden değil sözleşmeden gelir |
| Tanımsız Davranış | Dört tanımsız işlemin tanımlı karşılığı | sahipsiz | Çalışıyor görünmek tanımlı olduğunu göstermez |
| Hata Yönetimi Kalıpları | Hata yolunda temizlenen blok | çağrılan | Kısmi başarısızlıkta kimin temizleyeceği her işlevde ayrı yazılmalıdır |
| Sınır Denetimi | `snprintf` ile sınırlanan kopya | çağıran | İşlev yalnız kendisine söylenen sınırı denetler, yanlış bildirilen sınırı yakalayamaz |
| Taşınabilirlik | Sabit genişlikli tip, `sizeof(char)`, `INT_MAX`, sıfır boyutlu ayırma | dil | Yalıtım tekniğinin kendisi de bir garantiye dayanır: `int32_t`'nin var olması standart tarafından zorunlu değildir |

Dersler sınıflara **4 dil · 4 çağıran · 3 çağrılan · 4 sahipsiz** olarak dağılıyor — bu
derslerin dağılımıdır ve çekirdeğin on üç gözleminin 4/4/3/2'siyle karıştırılmaz.

Dört sınıf on beş ders boyunca hiç değişmedi, ama her ders sınıfı farklı bir yüzden gösterdi:
kimi zaman sahiplenen taraf bir işlevdi, kimi zaman bir sözleşmeydi, kimi zaman hiç kimseydi,
kimi zaman da bu son derste görüldüğü gibi dilin kendisiydi. Bir işaretçinin tipi hiçbir
zaman sahibini söylemedi; sahibi her seferinde ya kaynağın kendisi (bir `&` ifadesi, bir
ayırma çağrısı) ya da başlık dosyasındaki yazılı sözleşme belirledi.

Sıradaki kurs C ile sistem programlamaya, işletim sistemi arayüzlerine bakıyor. Bu kursun
sahiplik sorusu orada da geçerliliğini koruyor — bir dosya tanıtıcısını, bir süreci ya da bir
sinyali kim sahipleniyor sorusu, bellek için sorulan soruyla aynı biçimdedir; ama bu kaynakların
sınıflarını o kurs kendi ölçümüyle kuracak.

