---
title: 'C ile Sistem Programlama'
source: 'https://academia.sh/tr/kurslar/c-sistem-programlama'
language: tr
updated: '2026-09-26T03:02:44+00:00'
license: 'CC BY-SA 4.0'
lessons: 15
outcomes:
    - 'Dosya tanıtıcıları, süreçler ve sinyallerle işletim sistemi arayüzünü doğrudan kullanır'
    - 'Bir değişikliğin kopya, ortak, aktarım ya da gözlenmeyen olduğunu ayırt eder ve ölçer'
    - 'Boru, paylaşılan bellek ve iş parçacığıyla veriyi tanımlı yoldan paylaşır'
    - 'Bağlı yapıları, halka arabelleği ve karma tablosunu sahiplik sözleşmesiyle gerçekler'
    - 'Derleme, kütüphane, test ve hata ayıklama altyapısının hangi değişikliği kaçırdığını açıklar'
---

# C ile Sistem Programlama

Sistem programlamada bir değişikliği yapan taraf ile onu okuyacak taraf çoğu zaman aynı kopyayı tutmaz: kurs her düzeneği tek bir soruyla ölçer — bu değişikliği kim görüyor; yalnız yazanın kopyası mı, aynı nesneyi tutan herkes mi, bir aracıdan geçip okuyan mı, yoksa hiç kimse mi? Dosya tanıtıcıları, süreçler, sinyaller, iş parçacıkları, veri yapıları, derleme ve test altyapısı aynı çerçeveden geçirilir.

## Sistem Arayüzleri

Dosya tanıtıcıları ve paylaşılan dosya konumu, süreç oluşturma ve bekleme, sinyal işleme, boru, paylaşılan bellek ve soketle süreçler arası iletişim, ve POSIX iş parçacıklarında kilitle paylaşım — her biri bir değişikliği kimin gördüğü sorusuyla birlikte.

### Dosya Tanıtıcıları

Source: https://academia.sh/tr/kurslar/c-sistem-programlama/dosya-taniticilari

Bir işaretçinin aynı bellek konumunu gösterip göstermediği eşitlikle ölçülür; bir dosya tanıtıcısının aynı açık dosya kaydını paylaşıp paylaşmadığı ise konum davranışıyla ölçülür — tanıtıcının kendisi bir tamsayıdır ve hiçbir şey söylemez.

Önceki kurs bir bloğu kimin bırakacağını sordu: bir işaretçi bir kaydı gösterdiğinde,
o kaydı serbest bırakma yükümlülüğü hangi tarafa aitti. Bu kurs farklı bir soru sorar.
Bir taraf bir şeyi değiştirdiğinde öteki taraf bunu görür mü? `fork` belleği çoğaltır,
`dup` bir açık dosyayı paylaştırır, bir iş parçacığı adres uzayını paylaşır, bir boru
baytları bir kez taşır. Her düzenek bu soruya farklı yanıt verir, ve kurs boyunca bu
yanıtın dört sabit adı var: **kopya**, **ortak**, **aktarım**, **gözlenmeyen**. Bu ders
dördünü tek bir koşumda gösteriyor, sonra ilkine — dosya tanıtıcılarına — biraz daha
yakından bakıyor.

#### Kim Görüyor Sorusu: Çekirdeğin Tam Koşumu

Ölçüm iki parçadan kuruluyor. `gozlem.h` ve `gozlem.c`, bir değişikliğin adını, sınıfını
ve öteki tarafın onu görüp görmediğini kaydeden küçük bir birim. Sayılar yalnız
`gozlem.c` içinde tutuluyor; hiçbir ders bu sayılara dışarıdan dokunmuyor.

```c
// gozlem.h — bir degisikligin kime ulastigini siniflayan kayit
#ifndef GOZLEM_H
#define GOZLEM_H

enum Sinif { KOPYA, ORTAK, AKTARIM, GOZLENMEYEN };

/* Bir degisikligi kaydeder: gordu, degisikligi yapmayan tarafin onu
   okuyup okuyamadigidir (1 ya da 0). */
void gozlem(const char *degisiklik, enum Sinif sinif, int gordu);

/* Sinif basina sayilari ve ulasan / ulasmayan toplamini basar. */
void gozlem_ozeti(void);

#endif
```

```c
// gozlem.c — sayilar yalniz bu birimde tutulur
#include <stdio.h>
#include "gozlem.h"

static const char *const adlar[] = { "kopya", "ortak", "aktarim", "gozlenmeyen" };
static int sayilar[4];
static int ulasan = 0;
static int ulasmayan = 0;

void gozlem(const char *degisiklik, enum Sinif sinif, int gordu) {
    /* ortak ve aktarim ulasir, kopya ve gozlenmeyen ulasmaz */
    int beklenen = (sinif == ORTAK || sinif == AKTARIM);
    sayilar[sinif]++;
    if (gordu) {
        ulasan++;
    } else {
        ulasmayan++;
    }
    printf("%-34s %-8s %s%s\n", degisiklik, gordu ? "gordu" : "gormedi",
           adlar[sinif], gordu == beklenen ? "" : "  TUTARSIZ");
}

void gozlem_ozeti(void) {
    printf("siniflar: kopya %d, ortak %d, aktarim %d, gozlenmeyen %d\n",
           sayilar[KOPYA], sayilar[ORTAK], sayilar[AKTARIM], sayilar[GOZLENMEYEN]);
    printf("ulasan %d, ulasmayan %d\n", ulasan, ulasmayan);
}
```

Üçüncü birim, `gorur.c`, on beş küçük değişikliğin her birini bu iki işlevle ölçüyor.
Bu ders çekirdeğin **tamamını** koşturuyor; kalan on dört ders yalnız kendi kesitini
alacak, bu dosyayı yeniden yazmayacak.

```c
// gorur.c — on bes degisiklik, her biri icin tek soru: kim goruyor
#define _POSIX_C_SOURCE 200809L
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include "gozlem.h"

/* 1. Dosya tanitici: dup ile cogaltilan tanitici konumu paylasir */
static void tanitici(void) {
    int a = open("gorur.txt", O_CREAT | O_RDWR | O_TRUNC, 0600);
    if (write(a, "abcdef", 6) != 6) {
        return;
    }
    int b = dup(a);
    lseek(a, 2, SEEK_SET);
    gozlem("dup ile cogaltilan konum", ORTAK, lseek(b, 0, SEEK_CUR) == 2);
    close(b);
    close(a);
    unlink("gorur.txt");
}

/* 2. Surec: cocugun yazdigi degisken ebeveynde degismez */
static void surec(void) {
    int deger = 0;
    fflush(stdout);
    pid_t p = fork();
    if (p == 0) {
        deger = 1;
        _exit(deger);
    }
    int durum = 0;
    waitpid(p, &durum, 0);
    gozlem("fork sonrasi cocugun degiskeni", KOPYA, deger == 1);
}

/* 3. Sinyal: isleyicinin kaldirdigi bayrak ana akisa bir kez ulasir */
static volatile sig_atomic_t bayrak = 0;
static void isleyici(int s) {
    (void)s;
    bayrak = 1;
}
static void sinyal(void) {
    struct sigaction sa;
    memset(&sa, 0, sizeof sa);
    sa.sa_handler = isleyici;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGUSR1, &sa, NULL);
    raise(SIGUSR1);
    gozlem("isleyicinin kaldirdigi bayrak", AKTARIM, bayrak == 1);
}

/* 4. Boru: cocugun yazdigi deger ebeveyne bir kez tasinir */
static void boru(void) {
    int uc[2];
    if (pipe(uc) != 0) {
        return;
    }
    fflush(stdout);
    pid_t p = fork();
    if (p == 0) {
        int deger = 42;
        close(uc[0]);
        ssize_t n = write(uc[1], &deger, sizeof deger);
        _exit(n == (ssize_t)sizeof deger ? 0 : 1);
    }
    close(uc[1]);
    int okunan = 0;
    ssize_t n = read(uc[0], &okunan, sizeof okunan);
    waitpid(p, NULL, 0);
    close(uc[0]);
    gozlem("boruya yazilan deger", AKTARIM, n == (ssize_t)sizeof okunan && okunan == 42);
}

/* 5. Is parcacigi: kilitle artirilan sayac ana is parcacigina ulasir */
static int sayac = 0;
static pthread_mutex_t kilit = PTHREAD_MUTEX_INITIALIZER;
static void *artir(void *arg) {
    (void)arg;
    pthread_mutex_lock(&kilit);
    sayac = 7;
    pthread_mutex_unlock(&kilit);
    return NULL;
}
static void is_parcacigi(void) {
    pthread_t t;
    pthread_create(&t, NULL, artir, NULL);
    pthread_join(t, NULL);
    pthread_mutex_lock(&kilit);
    int gordu = (sayac == 7);
    pthread_mutex_unlock(&kilit);
    gozlem("is parcaciginin yazdigi sayac", ORTAK, gordu);
}

/* 6. Bagli yapi: ikinci isaretciden yapilan degisiklik listede gorunur */
struct Dugum {
    int deger;
    struct Dugum *sonraki;
};
static void bagli(void) {
    struct Dugum c = { 3, NULL };
    struct Dugum b = { 2, &c };
    struct Dugum a = { 1, &b };
    struct Dugum *takma = &b;
    takma->deger = 20;
    int toplam = 0;
    for (struct Dugum *d = &a; d != NULL; d = d->sonraki) {
        toplam += d->deger;
    }
    gozlem("takma adla degisen dugum", ORTAK, toplam == 24);
}

/* 7. Halka arabellek: konan oge alan tarafa bir kez ulasir */
enum { KAPASITE = 4 };
struct Halka {
    int ogeler[KAPASITE];
    int bas;
    int adet;
};
static int halka_koy(struct Halka *h, int oge) {
    if (h->adet == KAPASITE) {
        return 0;
    }
    h->ogeler[(h->bas + h->adet) % KAPASITE] = oge;
    h->adet++;
    return 1;
}
static int halka_al(struct Halka *h, int *oge) {
    if (h->adet == 0) {
        return 0;
    }
    *oge = h->ogeler[h->bas];
    h->bas = (h->bas + 1) % KAPASITE;
    h->adet--;
    return 1;
}
static void halka(void) {
    struct Halka h = { { 0 }, 0, 0 };
    int alinan = 0;
    halka_koy(&h, 5);
    int ilk = halka_al(&h, &alinan) && alinan == 5;
    int ikinci = halka_al(&h, &alinan);
    gozlem("halkaya konan oge", AKTARIM, ilk && !ikinci);
}

/* 8. Karma tablosu: tablo anahtarin kendi kopyasini tutar */
struct Girdi {
    char anahtar[16];
    int deger;
};
static void karma(void) {
    char tampon[16] = "elma";
    struct Girdi g;
    strcpy(g.anahtar, tampon);
    g.deger = 1;
    strcpy(tampon, "armut");
    gozlem("eklendikten sonra degisen anahtar", KOPYA, strcmp(g.anahtar, "armut") == 0);
}

/* 9. Nesne benzeri tasarim: ayni nesneyi tutan iki kullanici */
struct Hesap {
    int bakiye;
};
static void hesap_yatir(struct Hesap *h, int tutar) {
    h->bakiye += tutar;
}
static int hesap_bakiye(const struct Hesap *h) {
    return h->bakiye;
}
static void nesne(void) {
    struct Hesap hesap = { 0 };
    struct Hesap *birinci = &hesap;
    const struct Hesap *ikinci = &hesap;
    hesap_yatir(birinci, 50);
    gozlem("bir kullanicinin yaptigi yatirma", ORTAK, hesap_bakiye(ikinci) == 50);
}

/* 10. Temizleme: bosaltilmayan tampon _exit ile yok olur */
static void temizleme(void) {
    int uc[2];
    if (pipe(uc) != 0) {
        return;
    }
    fflush(stdout);
    pid_t p = fork();
    if (p == 0) {
        close(uc[0]);
        FILE *akis = fdopen(uc[1], "w");
        if (akis == NULL) {
            _exit(1);
        }
        setvbuf(akis, NULL, _IOFBF, 256);
        fputs("tamponda kalan satir\n", akis);
        _exit(0);
    }
    close(uc[1]);
    char gelen[64];
    ssize_t n = read(uc[0], gelen, sizeof gelen);
    waitpid(p, NULL, 0);
    close(uc[0]);
    gozlem("bosaltilmadan cikilan tampon", GOZLENMEYEN, n > 0);
}

/* 11. Derleme sistemi (model): bagimliligi yazilmamis birim yeniden derlenmez */
struct Birim {
    int baslik_bagimli_mi;
    int derlendigi_surum;
};
static void derle(struct Birim *b, int baslik_surumu, int baslik_zamani, int nesne_zamani) {
    if (b->baslik_bagimli_mi && baslik_zamani > nesne_zamani) {
        b->derlendigi_surum = baslik_surumu;
    }
}
static void derleme(void) {
    struct Birim birim = { 0, 1 };
    derle(&birim, 2, 20, 10);
    gozlem("bagimliligi yazilmamis baslik", KOPYA, birim.derlendigi_surum == 2);
}

/* 12. Kitaplik (model): statik baglama kodun bir kopyasini alir */
struct Kitaplik {
    int surum;
};
struct Program {
    int gomulu_surum;
    const struct Kitaplik *bagli;
};
static void kitaplik(void) {
    struct Kitaplik k = { 1 };
    struct Program statik = { k.surum, NULL };
    k.surum = 2;
    gozlem("statik bagli kitaplik guncellemesi", KOPYA, statik.gomulu_surum == 2);
}

/* 13. Test: ayri surecte kosan testin degisikligi sonrakine gecmez */
static int ortak_durum = 0;
static void test(void) {
    fflush(stdout);
    pid_t p = fork();
    if (p == 0) {
        ortak_durum = 9;
        _exit(0);
    }
    waitpid(p, NULL, 0);
    gozlem("yalitilmis testin degisikligi", KOPYA, ortak_durum == 9);
}

/* 14. Hata ayiklayici (model): iki durak arasindaki ara deger */
static void hata_ayiklayici(void) {
    int degerler[3] = { 1, 2, 3 };
    int gorulen[2];
    gorulen[0] = degerler[0]; /* birinci durak */
    gorulen[1] = degerler[2]; /* ikinci durak */
    gozlem("iki durak arasindaki ara deger", GOZLENMEYEN,
           gorulen[0] == 2 || gorulen[1] == 2);
}

/* 15. Eniyileme (model): hicbir okumanin gormedigi yazma */
struct Adim {
    char islem;
    int deger;
};
static void eniyileme(void) {
    struct Adim akis[] = { { 'y', 1 }, { 'y', 2 }, { 'o', 0 } };
    int okuyan = 0;
    int son_yazma = -1;
    for (int i = 0; i < 3; i++) {
        if (akis[i].islem == 'y') {
            son_yazma = i;
        } else if (son_yazma == 0) {
            okuyan++;
        }
    }
    gozlem("okunmadan ezilen yazma", GOZLENMEYEN, okuyan > 0);
}

int main(void) {
    tanitici();
    surec();
    sinyal();
    boru();
    is_parcacigi();
    bagli();
    halka();
    karma();
    nesne();
    temizleme();
    derleme();
    kitaplik();
    test();
    hata_ayiklayici();
    eniyileme();
    gozlem_ozeti();
    return 0;
}
```

```text
dup ile cogaltilan konum           gordu    ortak
fork sonrasi cocugun degiskeni     gormedi  kopya
isleyicinin kaldirdigi bayrak      gordu    aktarim
boruya yazilan deger               gordu    aktarim
is parcaciginin yazdigi sayac      gordu    ortak
takma adla degisen dugum           gordu    ortak
halkaya konan oge                  gordu    aktarim
eklendikten sonra degisen anahtar  gormedi  kopya
bir kullanicinin yaptigi yatirma   gordu    ortak
bosaltilmadan cikilan tampon       gormedi  gozlenmeyen
bagimliligi yazilmamis baslik      gormedi  kopya
statik bagli kitaplik guncellemesi gormedi  kopya
yalitilmis testin degisikligi      gormedi  kopya
iki durak arasindaki ara deger     gormedi  gozlenmeyen
okunmadan ezilen yazma             gormedi  gozlenmeyen
siniflar: kopya 5, ortak 4, aktarim 3, gozlenmeyen 3
ulasan 7, ulasmayan 8
```

**SA1.** On beş satırın hiçbiri `TUTARSIZ` işareti taşımıyor: her gözlem beklenen sınıfla
uyuşuyor. Beş değişiklik kopya, dört ortak, üç aktarım, üç gözlenmeyen — ve yedisi
öteki tarafa ulaşırken sekizi ulaşmıyor. Bu on beş satırlık liste kursun geri kalanının
haritası: her ders burada geçen bir satırı alıp derinleştiriyor. Bu dersin payı ilk
satır: `dup ile cogaltilan konum`.

#### dup ile Çoğaltılan Konum

**SA2.** `tanitici` işlevi bir dosyayı `a` tanıtıcısıyla açıyor, altı bayt yazıyor, sonra `dup(a)`
ile ikinci bir tanıtıcı — `b` — türetiyor. `a` üzerinden konum ikiye alınıyor;
ölçülen soru, `b` üzerinden okunan konumun da ikiye gelip gelmediği. Çıktı `gordu`
diyor, yani geldi. Bunun nedeni tanıtıcı sayılarında değil, işletim sisteminin tuttuğu
üç katmanlı yapıda: her süreç kendi tanıtıcı tablosunu tutar, her tanıtıcı bir **açık
dosya kaydına** işaret eder, her açık dosya kaydı da bir **konum** taşır. `open` her
çağrıldığında yeni bir açık dosya kaydı — ve onunla yeni bir konum — oluşturur. `dup`
ise yeni bir kayıt açmaz; var olan kaydı gösteren ikinci bir tanıtıcı verir. `a` ve `b`
farklı tamsayılar, ama aynı kaydı gösteriyorlar; konumu biri değiştirdiğinde öteki de
aynı konumu okuyor.

Aynı dosyayı `open` ile iki kez açmış olsaydık — `a`'yı bir kez, sonra aynı yolu ikinci
bir çağrıyla `c` olarak — durum farklı olurdu. İki ayrı açık dosya kaydı oluşurdu, ikisi
de kendi konumunu tutardı; `a` üzerinden konum ikiye alındığında `c` sıfırda kalırdı.
Kayıt paylaşımı `open` ile değil, aynı kaydı **gösteren** bir tanıtıcı türetmekle
kurulur: `dup`, `dup2`, ya da `fork` sırasında miras alınan tanıtıcılar. `gorur.c`
bu ayrımı `dup` ile gösteriyor; sonraki ders aynı mirası `fork`la sürdürecek, ama
orada paylaşılan konum değil, çoğaltılan bellek konusu.

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

Bir tanıtıcı yalnız küçük bir tamsayıdır; kendi başına hiçbir şey söylemez. İki tanıtıcı
aynı kayda mı, yoksa iki ayrı kayda mı bakıyor — bu, sayısal değerlerine bakarak asla
anlaşılmaz. Tek güvenilir ölçüm, kaydın taşıdığı **konumun** davranışıdır: biri
üzerinden değiştirilen konum ötekinden de okunuyorsa kayıt paylaşılıyordur; okunmuyorsa
iki ayrı kayıt vardır. `gorur.c` bu yüzden konum değerini `lseek(b, 0, SEEK_CUR)` ile
okuyor, tanıtıcı sayısını hiçbir yerde basmıyor — tanıtıcı numarası hedefe ve koşuma
bağlıdır, konum davranışı bağlı değildir.

#### Özet

- Kurs boyunca sabit kalan dört sınıf — kopya, ortak, aktarım, gözlenmeyen — tek bir
  soruya yanıt verir: değişikliği yapmayan taraf onu görüyor mu.
- Çekirdeğin on beş gözlemi beş kopya, dört ortak, üç aktarım, üç gözlenmeyen olarak
  dağılıyor; yedisi öteki tarafa ulaşıyor, sekizi ulaşmıyor.
- `dup` ile türetilen bir tanıtıcı yeni bir açık dosya kaydı açmaz; var olan kaydı
  gösteren ikinci bir yol açar, bu yüzden konumu paylaşır.
- Ayrı bir `open` çağrısı her zaman ayrı bir açık dosya kaydı, dolayısıyla ayrı bir
  konum yaratır — aynı dosyaya baksa bile.
- Bu dersin sınıfı **ortak**: iki tanıtıcı aynı kaydı tuttuğu için biri değiştirdiğinde
  öteki de görüyor.

#### Sonraki Adım

Bir sonraki ders aynı soruyu `fork`a soruyor. `dup` bir kaydı paylaştırdı; `fork` ise
belleği paylaştırmaz, çoğaltır — çocuğun değişkeni kendi kopyasında kalır, ebeveyn
hiçbir zaman görmez.

### Süreç Yönetimi

Source: https://academia.sh/tr/kurslar/c-sistem-programlama/surec-yonetimi

fork bir süreci ikiye ayırır ve o anki belleğin bir kopyasını çocuğa verir; kopya kurulduktan sonra iki taraf birbirinden bağımsızdır, hiçbir değişiklik öteki tarafa hiçbir yönde ulaşmaz.

Önceki ders `dup` ile türetilen bir tanıtıcının aynı açık dosya kaydını paylaştığını
gösterdi: biri konumu değiştirdiğinde öteki de gördü. `fork` bunun tam tersini yapar.
Çağrıldığı anda süreç belleğinin bir kopyasını çocuğa verir, ve o kopya kurulduktan
sonra iki taraf birbirinden tamamen bağımsızdır. Kurstaki dört sınıftan biri olan
**kopya**, tam olarak bunu tanımlıyor: değişiklik yalnız onu yapan tarafta kalır,
öteki taraf hiçbir zaman görmez. Bu ders bu bağımsızlığı iki yönde, iki farklı
`fork` çağrısında ve iki farklı veri türünde ölçüyor: önce iki düz tamsayı, sonra
çıkış durumunu okuyan makrolar üzerinden, en sonunda da standart çıktının
tamponunu taklit eden küçük bir yapı üzerinden.

#### İki Yönlü Kopya Ölçümü

Ölçüm birimleri değişmiyor: `gozlem.h` ve `gozlem.c`, önceki dersteki içerikleriyle
aynı. Bu dosyadaki yeni parça, iki `fork` çağrısını art arda kullanan
`surec_olcumu.c`: ilki iki düz tamsayıyı iki yönde ölçüyor, ikincisi standart
çıktının tamponunu taklit eden küçük bir yapıyı aynı şekilde ölçüyor.

```c
// gozlem.h — bir degisikligin kime ulastigini siniflayan kayit
#ifndef GOZLEM_H
#define GOZLEM_H

enum Sinif { KOPYA, ORTAK, AKTARIM, GOZLENMEYEN };

/* Bir degisikligi kaydeder: gordu, degisikligi yapmayan tarafin onu
   okuyup okuyamadigidir (1 ya da 0). */
void gozlem(const char *degisiklik, enum Sinif sinif, int gordu);

/* Sinif basina sayilari ve ulasan / ulasmayan toplamini basar. */
void gozlem_ozeti(void);

#endif
```

```c
// gozlem.c — sayilar yalniz bu birimde tutulur
#include <stdio.h>
#include "gozlem.h"

static const char *const adlar[] = { "kopya", "ortak", "aktarim", "gozlenmeyen" };
static int sayilar[4];
static int ulasan = 0;
static int ulasmayan = 0;

void gozlem(const char *degisiklik, enum Sinif sinif, int gordu) {
    /* ortak ve aktarim ulasir, kopya ve gozlenmeyen ulasmaz */
    int beklenen = (sinif == ORTAK || sinif == AKTARIM);
    sayilar[sinif]++;
    if (gordu) {
        ulasan++;
    } else {
        ulasmayan++;
    }
    printf("%-34s %-8s %s%s\n", degisiklik, gordu ? "gordu" : "gormedi",
           adlar[sinif], gordu == beklenen ? "" : "  TUTARSIZ");
}

void gozlem_ozeti(void) {
    printf("siniflar: kopya %d, ortak %d, aktarim %d, gozlenmeyen %d\n",
           sayilar[KOPYA], sayilar[ORTAK], sayilar[AKTARIM], sayilar[GOZLENMEYEN]);
    printf("ulasan %d, ulasmayan %d\n", ulasan, ulasmayan);
}
```

```c
// surec_olcumu.c — iki ayri fork, iki ayri veri turu, ayni kopyalanma kurali
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include "gozlem.h"

/* Model: gercek stdio tamponu degil, ayni kopyalanma kuralini izleyen kucuk bir yapi */
typedef struct {
    char veri[32];
    int uzunluk;
} ModelTampon;

static void model_yaz(ModelTampon *t, const char *s) {
    int n = 0;
    while (s[n] != '\0' && t->uzunluk < 31) {
        t->veri[t->uzunluk] = s[n];
        t->uzunluk++;
        n++;
    }
}

int main(void) {
    int deger_a = 0;
    int deger_b = 0;

    fflush(stdout);
    pid_t p = fork();
    if (p == 0) {
        deger_a = 1;      /* cocugun kendi kopyasinda degisir */
        _exit(deger_b);   /* ebeveynin sonraki degisikligini gormeden cikar */
    }

    deger_b = 7;           /* fork sonrasi yalniz ebeveynin kopyasinda degisir */
    int durum = 0;
    waitpid(p, &durum, 0);

    gozlem("fork sonrasi cocugun degiskeni", KOPYA, deger_a == 1);
    gozlem("fork sonrasi ebeveynin degiskeni", KOPYA, WEXITSTATUS(durum) == 7);
    printf("cocugun normal yoldan cikisi (WIFEXITED): %s\n", WIFEXITED(durum) ? "evet" : "hayir");

    /* ikinci fork: model tampon da ayni kurala uyuyor mu */
    ModelTampon tampon;
    memset(&tampon, 0, sizeof tampon);
    model_yaz(&tampon, "onceki");   /* fork oncesi birikmis, henuz bosaltilmamis icerik modeli */

    fflush(stdout);
    pid_t p2 = fork();
    if (p2 == 0) {
        model_yaz(&tampon, "cocuk");
        _exit(tampon.uzunluk);
    }
    model_yaz(&tampon, "ebeveyn");
    int durum2 = 0;
    waitpid(p2, &durum2, 0);

    gozlem("cocugun tampona eklemesi ebeveyne ulasti mi", KOPYA,
           WEXITSTATUS(durum2) == tampon.uzunluk);

    gozlem_ozeti();
    return 0;
}
```

```text
fork sonrasi cocugun degiskeni     gormedi  kopya
fork sonrasi ebeveynin degiskeni   gormedi  kopya
cocugun normal yoldan cikisi (WIFEXITED): evet
cocugun tampona eklemesi ebeveyne ulasti mi gormedi  kopya
siniflar: kopya 3, ortak 0, aktarim 0, gozlenmeyen 0
ulasan 0, ulasmayan 3
```

**SA3.** İlk iki satır da `gormedi` diyor. Çocuk `deger_a`'yı kendi kopyasında `1`
yapıyor; ebeveyn `waitpid`'den sonra kendi `deger_a`'sına bakıyor ve hâlâ `0`
buluyor — çocuğun yazması hiçbir zaman ebeveynin kopyasına ulaşmadı. Simetrik
yönde, ebeveyn `fork` döndükten sonra kendi `deger_b`'sini `7` yapıyor; çocuk bu
satırı hiç görmeden `_exit(deger_b)` ile çıkıyor, ve kendi kopyasındaki `deger_b`
hâlâ `0` olduğu için çıkış kodu da `0`. Ebeveyn bunu `WEXITSTATUS` ile okuyup `7`
değil `0` bulduğunda, kendi değişikliğinin çocuğa hiç ulaşmadığını doğruluyor.
İkisi de aynı sınıfa düşüyor: **kopya**. `fork` bir kere kopyalar, sonra iki taraf
birbirinden habersizdir — kim önce, kim sonra yazdığının burada hiçbir önemi yok,
çünkü paylaşılan bir kayıt yok, iki bağımsız kopya var.

#### fflush Kuralının Ölçümü

**SA4.** Üçüncü satır bu bağımsızlığı bir tamsayıdan çıkarıp çok satırlı bir
tampona taşıyor. `ModelTampon`, gerçek `stdio` tamponunun yerini tutan bir model:
`fork`tan önce `"onceki"` metnini biriktiriyor, tıpkı boşaltılmamış bir çıktı
satırının terminale yazılmayı beklemesi gibi. `fork` çağrıldığında bu yapı da
belleğin geri kalanıyla birlikte kopyalanıyor — çocuk kendi kopyasına `"cocuk"`
ekliyor ve uzunluğunu çıkış koduyla bildiriyor, ebeveyn kendi kopyasına `"ebeveyn"`
ekliyor. `"cocuk"` beş, `"ebeveyn"` yedi karakter olduğu için iki tarafın son
uzunluğu hiçbir zaman eşit çıkmıyor, ve ölçüm bunu `gormedi` olarak okuyor: çocuğun
kendi tamponuna yaptığı ekleme ebeveynin tamponuna hiç ulaşmadı. Kurstaki her
derste geçerli olan kural tam olarak bu ölçümün gösterdiği şey: `fork`'tan önceki
`fflush(stdout)` çağrısı, gerçek `stdio` tamponunun içindeki boşaltılmamış içeriği
sıfırlar, böylece kopyalanacak bir şey kalmaz. Bu satır atlanırsa, `"onceki"` gibi
henüz yazılmamış bir içerik hem ebeveynin hem çocuğun kopyasında birlikte durur;
ikisi de kendi kapanışında kendi tamponunu boşaltırsa aynı satır iki kez
görünür — bir kez ebeveynden, bir kez çocuktan, çünkü artık ikisi de aynı sınıfa,
yani **kopya**'ya ait ayrı birer nüsha taşıyor. `surec_olcumu.c` bu riski iki kez
kapatıyor: her iki `fork`tan hemen önce `fflush(stdout)` çağrılıyor, ve `gozlem`
çağrılarının kendisi yalnız `waitpid` sonrası ebeveyn tarafında koşuyor; çocuk
hiçbir zaman yazdırmıyor, `_exit` ile sessizce çıkıyor.

#### Çıkış Durumu Makroları

**SA5.** `waitpid`'in doldurduğu `durum` tek bir tamsayı ama içinde iki ayrı soru
kodlanmış: süreç nasıl bitti, ve hangi değerle. `WEXITSTATUS(durum)` yalnız ikinci
soruyu yanıtlıyor — çocuğun `_exit`'e ya da `return`'e verdiği değeri döndürüyor —
ama bu makro yalnız birinci soru olumluysa, yani çocuk gerçekten normal yoldan
çıktıysa anlamlı. `WIFEXITED(durum)` birinci soruyu yanıtlıyor: çocuğun bir
sinyalle değil, kendi isteğiyle sonlandığını doğruluyor. Bu ders `WIFEXITED`'i
her iki `fork`tan sonra da doğru buluyor, çünkü iki çocuk da `_exit` ile çıkıyor;
gelecek dersin sinyal konusu bu makronun karşıtını, yani bir sinyalle sonlanan
bir süreç görene kadar bu ayrım soyut kalır. Buradaki nokta şu: `WEXITSTATUS`'a
`WIFEXITED` kontrolü olmadan güvenmek yanıltıcı olabilir, çünkü sinyalle
sonlanan bir sürecin `durum` alanı aynı bitleri başka bir amaçla taşır — bu ders
bunu koşturmuyor, ama makronun neden yalnız başına yeterli olmadığını burada
kaydediyor.

`WEXITSTATUS`'un döndürdüğü değer sekiz bitle sınırlıdır, çünkü POSIX çıkış
kodunu bu genişlikte taşımayı garanti eder; `surec_olcumu.c` içindeki iki ölçüm
de bu sınırın altında kalacak şekilde seçildi — `deger_b` yedi, model tamponun
uzunluğu otuz birin altında. Bir programın `_exit`'e üç haneli ya da negatif bir
değer verip beklentisiyle çıkış kodunu okuması, bu sekiz bitlik kesilme yüzünden
yanıltıcı sonuç üretebilir; bu ders o durumu ayrıca ölçmüyor, ama `gozlem`
çağrılarının küçük sabit değerler seçmesinin nedeni tam olarak bu sınırdan
kaçınmak.

`WIFEXITED`'in karşıtı `WIFSIGNALED`'dir: çocuk bir sinyal yüzünden sonlandıysa
bu makro doğru döner, ve o durumda `WTERMSIG(durum)` hangi sinyalin sonlandırdığını
okur. Bu ders sinyal numarası ya da adı yazdırmayı yasaklayan kural gereği bu
makroyu koşturmuyor — ne yazdırabileceği bir değer üretir ne de bu dersin ölçüm
eksenine bir şey katar, çünkü sinyalle sonlanan bir sürecin çıkış kodu diye bir
şey yoktur, yalnız sonlandıran sinyal vardır. Bir programın `waitpid` sonrası
hem `WIFEXITED` hem `WIFSIGNALED`'i kontrol etmeden `WEXITSTATUS`'a güvenmesi,
sinyalle sonlanan bir çocukta tanımsız bir değeri çıkış koduymuş gibi okumasına
yol açar; bu yüzden ikisi arasındaki ayrım yalnız bir stil tercihi değil, doğru
okumanın önkoşuludur.

#### exec Ailesi

**SA6.** `fork`tan sonra çocuk sürecinde çoğunlukla ikinci bir adım gelir: `exec`
ailesinden bir çağrı. `fork` belleği kopyalarken program kodunu değiştirmez —
çocuk hâlâ aynı programın bir kopyasıdır. `exec` ise süreç kimliğini korur ama
süreç **görüntüsünü** baştan kurar: kod, veri ve yığıt yeni bir programınkiyle
değiştirilir, ve çağrı başarılı olduğunda geri dönmez. Bu ders `exec`'i
koşturmuyor, çünkü ölçtüğü soru — kim neyi görüyor — `exec` sonrasında anlamını
yitirir: eski sürecin değişkenleri, `deger_a`'sı, `deger_b`'si, model tamponu
artık yok, yerlerine yenisi geldi. `fork` ve `exec`'in birlikte kullanımı, bir
kabuğun yeni bir program başlatmasının temelidir: kabuk önce `fork` ile kendi
kopyasını üretir, çocuk süreç ortam değişkenlerini ve açık tanımlayıcıları kalıtım
yoluyla devralır, sonra `exec` ile o kopyanın üzerine istenen programı yükler.
Ebeveyn kabuk hiçbir zaman değişmez; yalnız çocuk `exec` çağırır, ve başarılı
olduğunda artık kabuk değil, çağrılan programdır.

`exec` ailesinin üyeleri birbirinden üç eksende ayrılır, hepsi düzyazıyla:
argümanları nasıl aldıkları (`execl` gibi değişken sayıda argümanla çağrılanlar,
`execv` gibi bir dizi alanlar), programı nasıl buldukları (`execve` yalnız tam
yolu kabul eder, `execvp` `PATH` ortam değişkeninde arama yapar) ve ortamı nasıl
devraldıkları (`execve` ve `execvpe` yeni bir ortam dizisi kabul eder, diğerleri
çağıran sürecin ortamını olduğu gibi kullanır). Hepsinin ortak noktası dönüş
değeri yoktur — çağrı başarılıysa geri dönmez, başarısızsa `-1` döner ve çağıran
süreç kendi değişkenleriyle devam eder. `fork` olmadan çağrılan bir `exec`,
çağıran süreci geri dönülmez biçimde başka bir programa çevirir; bu yüzden bir
kabuğun kendi sürecini kaybetmeden yeni bir program başlatması, `fork` ile önce
bir kopya üretip `exec`i o kopyada çağırmasına bağlıdır. Açık dosya tanımlayıcıları
varsayılan olarak `exec` sonrasında da açık kalır — `FD_CLOEXEC` bayrağı
işaretlenmediği sürece çocuğun `fork`tan miras aldığı tanımlayıcılar yeni
programda da geçerlidir; bir kabuğun yönlendirme (`>`, `<`) sözdizimini
gerçeklemesi tam olarak buna dayanır: kabuk önce tanımlayıcıyı `dup2` ile istenen
konuma taşır, sonra `exec` çağırır, ve yeni program o tanımlayıcının nereden
geldiğini hiç bilmeden kullanır.

`fork`un bellek kopyasını nasıl ürettiği bu dersin konusu değil; işletim sistemleri
kursunun bellek yönetimi bölümü bu soruyu zaten yanıtladı — çekirdek çoğu zaman
sayfaları gerçekten çoğaltmaz, yazma-üzerine-kopyala düzeniyle işaretler ve gerçek
kopya yalnız bir taraf yazdığında oluşur. Bu ders o iç işleyişe dokunmuyor; ölçtüğü
tek şey, kopyalama hangi düzenekle yapılırsa yapılsın iki tarafın sonuçta bağımsız
görünmesi — ister bir tamsayı, ister çok satırlı bir tampon olsun.

`ModelTampon` ölçümü gerçek bir stdio tamponunun dahili yapısını taklit
etmiyor; yalnız aynı kopyalanma kuralına uyan, kendi tanımladığımız küçük bir
yapı. Buradaki genellik önemli: gerçek `FILE*` tamponu içinde tutulan bayt
dizisi de, bu dersin başka yerinde ölçtüğü bir tamsayı değişkeni de, `fork`
anında aynı kopyalama kuralına tabidir — çocuk ebeveynin o anki durumunu alır,
sonrasında ikisi bağımsız değişir. `ModelTampon` bu genelliği somutlaştırıyor:
üç bayt uzunluğundaki bir sayaç ile otuz iki baytlık bir dizi, `fork` açısından
eşit muamele görür. Gerçek stdio'da fark, tamponun ne zaman boşaldığını
programcının değil kütüphanenin kararının belirlemesi; satır tamponlu bir
terminalde her satır sonunda, tamamen tamponlu bir dosyada yalnız tampon
dolduğunda ya da program kapanırken boşalır. `fork` bu boşalma zamanlamasını
değiştirmez, yalnız o ana kadar birikmiş içeriği iki bağımsız kopyaya böler;
kuralı ihlal eden `fflush(stdout)` çağrısının eksikliği değil, birikmiş
içeriğin iki kez boşalacağı beklentisidir.

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

Bu ölçümün sınırı zamanlamadan bağımsız olmasıdır, ve bu kasıtlı bir tasarım. Çocuğun
`_exit` çağrısı ile ebeveynin sonraki yazması hangi sırayla çalışırsa çalışsın
sonuç değişmez, çünkü ikisi aynı belleğe yazmıyor — her biri kendi kopyasına
yazıyor. Bu yüzden ölçüm bir `sleep` ya da bekleme gerektirmiyor; `waitpid`
çağrısının kendisi yeterli, çünkü sıralamayı değil kopyaların bağımsızlığını
doğruluyor. Model tampon ölçümü de aynı sınırı taşıyor: `"cocuk"` ile `"ebeveyn"`
metinlerinin farklı uzunlukta seçilmesi rastgele değil — sonucun her koşumda
aynı çıkması için kasıtlı, çünkü bu ders bir yarış koşulunu değil yalnız
kopyalanmanın varlığını ölçüyor. Kurstaki diğer sınıflar — özellikle aktarım —
sıralamaya duyarlı olabilir, ama kopya sınıfı için sıralama hiçbir zaman sonucu
değiştirmez.

Bu ölçümün kapsamadığı bir başka soru, kaç kez `fork` çağrıldığında sonucun nasıl
değiştiğidir. `surec_olcumu.c` iki `fork`u art arda, ilkinin çocuğu tamamen
kapandıktan sonra çağırıyor — ikinci `fork` birinci çocuğun süreç tablosundan
düşmesini beklemiyor, çünkü `waitpid` zaten birincisini topladı. Bir program bunun
yerine iki çocuğu aynı anda, ikincisini birincisinin bitişini beklemeden
başlatsaydı, iki çocuk da aynı ebeveyn belleğinin ayrı birer kopyasını taşırdı;
kopya sınıfının garantisi çocuk sayısından bağımsızdır, çünkü her `fork` çağrısı
kendi başına yeni ve bağımsız bir kopya üretir, önceki çocuklardan habersiz. Bu
ders tek seferde birden çok çocuk üretmiyor, çünkü sonucun gösterdiği şey zaten
tek bir çift üzerinden tam olarak kanıtlanıyor; ekstra çocuklar aynı garantiyi
tekrarlardı, yeni bir sınıf ya da yeni bir sınır getirmezdi.

#### Özet

- `fork`, çağrıldığı andaki bellek durumunun bir kopyasını çocuğa verir; kopya
  kurulduktan sonra iki taraf bağımsızdır.
- Bu bağımsızlık iki yönlüdür: çocuğun yazdığı bir değişken ebeveyne ulaşmaz,
  ebeveynin `fork` sonrası yazdığı bir değişken de çocuğa ulaşmaz.
- Aynı bağımsızlık tek bir tamsayıyla sınırlı değil; çok satırlı bir tampon gibi
  daha büyük bir yapı da aynı kurala uyar.
- `fflush(stdout)`, `fork`'tan önce zorunludur; aksi hâlde tamponlanmış içerik
  kopyalanır ve aynı satır iki kez yazılabilir.
- `WIFEXITED` ve `WEXITSTATUS` birlikte okunur: biri sürecin nasıl bittiğini,
  öteki hangi değerle bittiğini söyler; biri olmadan öteki yanıltıcı olabilir.
- `exec` ailesi süreç görüntüsünü baştan kurar ve geri dönmez; bu ders onu
  koşturmuyor, çünkü kopyalanan değişkenlerin sorusu `exec` sonrasında anlamsızlaşıyor.

#### Sonraki Adım

Bir sonraki ders `fork`un kopyaladığı değişkenlerden bambaşka bir taşıma biçimine
geçiyor: bir sinyal işleyicisinin kaldırdığı bir bayrak, ana akışa yalnız bir kez
ulaşan bir aktarım.

### Sinyaller

Source: https://academia.sh/tr/kurslar/c-sistem-programlama/sinyaller

Bir sinyal isleyicisi ana akisin disinda, beklenmedik bir noktada calisir; guvenli tek paylasim bicimi volatile sig_atomic_t bir bayraktir, ve bir sinyal engellenmisken tekrar gelirse ac ldiginda yalniz bir kez teslim edilir.

`fork` bir kopya verdi, iki taraf birbirinden habersiz kaldı. Bu ders farklı bir taşıma
biçimi ölçüyor: bir **sinyal**. Süreç normal akışını sürdürürken çekirdek onu durdurup
kayıtlı bir işleyiciyi çalıştırır, işleyici döner, akış kaldığı yerden sürer. İşleyici
ile ana akış aynı bellek alanını paylaşıyor, ama bu paylaşım `fork`taki gibi bir
kopyalama değil — işleyicinin bıraktığı iz, ana akışa **bir kez** ulaşan bir aktarım.
Kurstaki sınıflardan **aktarım**, tam olarak bunu tanımlıyor.

Bu ders `raise(SIGUSR1)` kullanıyor, `kill` ya da dış bir kaynak değil — ölçümün
kendi kendine yeterli olması için. Gerçek kullanımda bir sinyal üç kaynaktan
gelebilir: başka bir süreç `kill` çağrısıyla gönderir, çekirdek bir donanım
durumuna (geçersiz bellek erişimi, aritmetik hata) tepki olarak üretir, ya da
süreç kendi kendine `raise` ile gönderir. Üçü de aynı teslim mekanizmasını
kullanır — `isleyici` hangi yoldan geldiğini ayırt edemez, yalnız hangi sinyal
numarasının geldiğini bilir. `SIGUSR1` seçimi de rastgele değil: POSIX bu ikiliyi
(`SIGUSR1`, `SIGUSR2`) özel olarak uygulamaya bırakır, çekirdek onlara önceden
tanımlı bir anlam yüklemez; bu yüzden bu ölçüm, `SIGINT` ya da `SIGTERM` gibi
önceden tanımlı bir davranışı olan bir sinyalin varsayılan işleyicisini
değiştirme riskine girmeden çalışabiliyor.

#### İşleyicinin Kaldırdığı Bayrak

Ölçüm birimleri aynı kalıyor. Bu dersin kendi dosyası `sinyal_olcumu.c`, üç ayrı
soruyu ölçüyor: işleyicinin kaldırdığı bayrak ana akışa ulaşıyor mu, engellenmiş
bir sinyal maskeliyken çekirdek onu bekletilmiş olarak görünür kılıyor mu, ve
maske kalkınca kaç kez teslim ediliyor.

```c
// gozlem.h — bir degisikligin kime ulastigini siniflayan kayit
#ifndef GOZLEM_H
#define GOZLEM_H

enum Sinif { KOPYA, ORTAK, AKTARIM, GOZLENMEYEN };

/* Bir degisikligi kaydeder: gordu, degisikligi yapmayan tarafin onu
   okuyup okuyamadigidir (1 ya da 0). */
void gozlem(const char *degisiklik, enum Sinif sinif, int gordu);

/* Sinif basina sayilari ve ulasan / ulasmayan toplamini basar. */
void gozlem_ozeti(void);

#endif
```

```c
// gozlem.c — sayilar yalniz bu birimde tutulur
#include <stdio.h>
#include "gozlem.h"

static const char *const adlar[] = { "kopya", "ortak", "aktarim", "gozlenmeyen" };
static int sayilar[4];
static int ulasan = 0;
static int ulasmayan = 0;

void gozlem(const char *degisiklik, enum Sinif sinif, int gordu) {
    /* ortak ve aktarim ulasir, kopya ve gozlenmeyen ulasmaz */
    int beklenen = (sinif == ORTAK || sinif == AKTARIM);
    sayilar[sinif]++;
    if (gordu) {
        ulasan++;
    } else {
        ulasmayan++;
    }
    printf("%-34s %-8s %s%s\n", degisiklik, gordu ? "gordu" : "gormedi",
           adlar[sinif], gordu == beklenen ? "" : "  TUTARSIZ");
}

void gozlem_ozeti(void) {
    printf("siniflar: kopya %d, ortak %d, aktarim %d, gozlenmeyen %d\n",
           sayilar[KOPYA], sayilar[ORTAK], sayilar[AKTARIM], sayilar[GOZLENMEYEN]);
    printf("ulasan %d, ulasmayan %d\n", ulasan, ulasmayan);
}
```

```c
// sinyal_olcumu.c — isleyicinin kaldirdigi bayrak ve engellenmis teslim sayisi
#define _POSIX_C_SOURCE 200809L
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include "gozlem.h"

static volatile sig_atomic_t bayrak = 0;
static volatile sig_atomic_t teslim_sayisi = 0;

static void isleyici(int s) {
    (void)s;
    bayrak = 1;
    teslim_sayisi++;
}

int main(void) {
    struct sigaction sa;
    memset(&sa, 0, sizeof sa);
    sa.sa_handler = isleyici;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGUSR1, &sa, NULL);

    /* birinci olcum: isleyicinin kaldirdigi bayrak ana akisa ulasiyor mu */
    raise(SIGUSR1);
    gozlem("isleyicinin kaldirdigi bayrak", AKTARIM, bayrak == 1);

    /* ikinci olcum: engellenmisken iki kez gelen sinyal, acilinca bir kez teslim edilir */
    sigset_t kume;
    sigemptyset(&kume);
    sigaddset(&kume, SIGUSR1);
    sigprocmask(SIG_BLOCK, &kume, NULL);

    teslim_sayisi = 0;
    raise(SIGUSR1);
    raise(SIGUSR1);
    int engelliyken = (teslim_sayisi == 0);

    /* ucuncu olcum: maske altindayken sinyal bekletilmis olarak goruluyor mu */
    sigset_t bekleyenler;
    sigpending(&bekleyenler);
    int bekliyor = sigismember(&bekleyenler, SIGUSR1);
    gozlem("maskeliyken sinyalin bekletilmis gorunmesi", AKTARIM,
           engelliyken && bekliyor == 1);

    sigprocmask(SIG_UNBLOCK, &kume, NULL);
    gozlem("acilista teslim edilen sinyal sayisi", AKTARIM,
           engelliyken && teslim_sayisi == 1);

    gozlem_ozeti();
    return 0;
}
```

```text
isleyicinin kaldirdigi bayrak      gordu    aktarim
maskeliyken sinyalin bekletilmis gorunmesi gordu    aktarim
acilista teslim edilen sinyal sayisi gordu    aktarim
siniflar: kopya 0, ortak 0, aktarim 3, gozlenmeyen 0
ulasan 3, ulasmayan 0
```

**SA7.** Birinci ölçüm dosdoğru: `raise(SIGUSR1)` çekirdeğe süreci durdurmasını ve `isleyici`yi
çalıştırmasını söylüyor, `isleyici` `bayrak`ı `1` yapıyor, `raise` döndükten sonra ana
akış bu bayrağı okuyor ve `1` buluyor. Bayrak `volatile sig_atomic_t` türünde — bu
tür seçimi rastgele değil, bir kuralın karşılığı: derleyici bu türdeki bir değişkeni
her okumada bellekten yeniden okumak, ve okuma ya da yazmayı yarım bırakmamak
zorunda. İşleyici ana akıştan bağımsız, beklenmedik bir noktada araya giriyor;
`volatile` olmayan bir değişken derleyici tarafından bir yazmaçta tutulabilir, ve
işleyicinin yaptığı değişiklik ana akışın gördüğü kopyaya hiç yansımayabilir.

#### Maske Altında Bekletme

**SA8.** İkinci ölçüm doğrudan bekletilmiş durumu gözlemliyor. `sigprocmask(SIG_BLOCK, ...)`
çağrısından sonra gönderilen bir sinyal hemen teslim edilmez; çekirdek onu
**bekletilmiş** olarak tutar, ama bu durum önceki ölçümdeki gibi dolaylı biçimde
(bir sayacın artmaması üzerinden) değil, `sigpending` ile **doğrudan** okunabilir:
`sigpending(&bekleyenler)` çağrısı, çağıran sürecin engellenmiş, henüz teslim
edilmemiş sinyallerinin kümesini doldurur, ve `sigismember(&bekleyenler, SIGUSR1)`
bu kümede `SIGUSR1`in olup olmadığını sorar. İki `raise` çağrısından sonra, teslim
gerçekleşmeden önce bu sorgu `1` döner — çekirdek sinyali kaybetmemiş, işlememiş,
bir yerde tutuyor. Bu, "bekletme" sözcüğünün düzyazı bir tasviri değil, çalıştırılabilir
bir ölçüm: `sinyal_olcumu.c` maskenin altında gerçekten bir durumun var olduğunu,
o durumun süreç tarafından sorgulanabilir olduğunu gösteriyor.

**SA9.** Üçüncü ölçüm bu bekletilmiş durumun **açılışta** ne yaptığını sayıyor. Aynı sinyal
engellenmişken iki kez `raise` edilse bile, çoğu standart sinyal için bekletilmiş
durum tekildir — teslim edilecek kopya sayısı değil, yalnız "bekleyen var mı"
bilgisi tutulur. Maske kalktığında (`SIG_UNBLOCK`) çekirdek bekletilmiş sinyali
teslim eder, ama yalnız **bir kez**: `teslim_sayisi` engelli süre boyunca `0`
kalıyor, açılışta `1`e çıkıyor, `2`ye değil. `sigpending` ölçümüyle birlikte
okununca bu iki bulgu tek bir resim veriyor: engelliyken kümede **bir bit**
bilgi var — "bekleyen var" ya da "yok" — ve bu bit, kaç kez `raise` çağrıldığından
bağımsız. Bit açılışta bir teslime dönüşüyor, sayaç değeri kadar teslime değil.

`sigaction`ın kendisi de aynı tekillik kuralını, işleyici çalışırken kullanıyor.
`sa.sa_mask` boş küme olarak ayarlandı, ama çekirdek yine de her işleyici çağrısı
sırasında **o sinyalin kendisini** otomatik olarak engeller — `SA_NODEFER` bayrağı
açıkça istenmedikçe, bir işleyici kendi çalıştığı sinyalin ikinci bir kopyasıyla
yeniden girilmez. Bu, `sinyal_olcumu.c`'nin ölçtüğü "maske altında bekletme"nin
küçük bir örneği: işleyici içindeyken gelen aynı sinyal kaybolmuyor, işleyici
dönene kadar bekletiliyor, sonra tek seferde teslim ediliyor. `sa_mask` alanı
bu otomatik engellemeyi genişletmek için var — bir işleyici birden fazla sinyal
türüyle ilgileniyorsa, aralarındaki yarışı önlemek için birbirlerini de bu alana
eklemek gerekir; bu ders tek sinyalle çalıştığı için `sigemptyset` ile boş bırakıyor.

Engellemenin bir başka kullanımı, kontrol-ve-eylem yarışını önlemektir. Bir program
bir bayrağı okuyup ona göre bir işlem yapacaksa, okuma ile işlem arasına bir sinyal
girip bayrağı değiştirebilir; bu ders bunu sınamıyor, ama örüntü kısaca şudur: ilgili
sinyal `sigprocmask(SIG_BLOCK, ...)` ile engellenir, bayrak okunur ve karar verilir,
sonra maske geri açılır. Böylece "oku ve karar ver" adımı sinyalin arasına girmesine
kapalı bir pencere içinde kalır — `sinyal_olcumu.c`'deki `sigpending` ölçümü, bu
pencerenin içinde bekletilmiş bir sinyalin gerçekten orada durduğunu, kaybolmadığını
doğrulayan aynı mekanizmaya dayanıyor.

#### İşleyici İçinde Tanımsız Davranış

**SA10.** `isleyici` işlevi yalnız iki satır: bir bayrağı kaldırmak ve bir sayacı artırmak.
Bu kısalık kural gereği, tercih değil. Bir sinyal işleyicisi ana akışın **herhangi**
bir noktasında araya girebilir — bir `malloc` çağrısının ortasında, bir kilidi tutan
bir kod parçasının içinde, hatta `printf`in kendi iç tamponunu güncellediği bir anda.
İşleyici içinde eşzamansız-sinyal-güvenli olmayan bir çağrı yapmak — `malloc`,
`printf`, kilitli bir kaynağa erişen herhangi bir işlev — kesintiye uğrattığı kodla
çakışabilir; bu **tanımsız davranıştır** ve bu ders böyle bir çağrıyı koşturmuyor,
yalnız burada tarif ediyor. Aynı şekilde, işleyici ile ana akış arasında `volatile`
olmayan bir değişken paylaşmak da tanımsız davranıştır: derleyici bu değişkenin
işleyici tarafından değiştirilebileceğini bilmediği için, ana akıştaki okumaları
yeniden düzenleyebilir ya da tamamen atlayabilir. `sinyal_olcumu.c` bu yüzden yalnız
`sig_atomic_t` türünde, `volatile` işaretli değişkenlere dokunuyor, ve işleyici
içinde hiçbir işlev çağırmıyor. Bu kısıt yalnız "iyi bir alışkanlık" değil; kesintiye
uğrayan çağrı bir kütüphanenin dahili kilidini ya da statik tamponunu tam ortada
tutarken işleyici aynı kilidi ya da tamponu tekrar kullanmaya kalkarsa sonuç
kilitlenme, bozulmuş veri ya da rastgele bir çökme olabilir — derleyici ya da
çalışma zamanı burada hiçbir hata bildirmez, davranış tanım dışına çıkar.

POSIX, işleyici içinde çağrılması güvenli olan küçük bir işlev kümesi tanımlar —
`write`, `_exit`, `sigprocmask`, bu dersin kullandığı `sigpending` gibi doğrudan
sistem çağrıları bu listede, ama tamponlu giriş/çıkış (`printf`, `fopen`), bellek
ayırma (`malloc`, `free`) ve çoğu kütüphane işlevi listede değildir. Bu ayrımın
nedeni tamponlama ve dahili durum: `printf` kendi iç tamponunu ve olası bir kilidini
günceller, bu güncelleme yarım kalmışken aynı işlevin işleyici içinde tekrar
çağrılması o dahili durumu tutarsız bırakabilir. `sinyal_olcumu.c` yalnız
`bayrak = 1` ve `teslim_sayisi++` yapıyor — ikisi de doğrudan bellek yazması,
hiçbir dahili durum ya da kilit içermiyor, bu yüzden işleyici içinde güvenle
çalışıyor.

Bu ayrım, önceki kursun sorduğu "adımı hangi iş parçacığı koşturuyor" sorusundan
farklı bir eksen: bir sinyal işleyicisi ayrı bir iş parçacığı değildir, aynı akışın
beklenmedik bir noktada kesilmesidir. Kesintiye uğrayan kod ile işleyici aynı yığıtı
ve aynı kayıt kümesini geçici olarak paylaşır — bu yüzden işleyicinin bıraktığı iz,
`fork`un verdiği bir kopya değil, aynı akışa dönen bir aktarımdır.

Bu paylaşım çok iş parçacıklı bir süreçte daha da daralır: POSIX'te bir sinyal
işleyicisi süreç düzeyinde kaydedilir, ama hangi iş parçacığının onu çalıştıracağı
teslime bağlıdır — bir iş parçacığının kendine yönelttiği bir sinyal (`pthread_kill`
ile) o iş parçacığında çalışır, sürece yönelen bir sinyal ise maskelenmemiş
herhangi bir iş parçacığında çalışabilir. Bu ders tek iş parçacıklı çalıştığı için
bu belirsizlik ortaya çıkmıyor; sonraki ders birden çok iş parçacığını tanıttığında,
sinyal işleyicisinin hangi akışta çalışacağı sorusu da o dersin kapsamı dışında
kalan, ama bilinmesi gereken bir sınır olarak duruyor.

**SA11.** M08/K04 dersi Java'da `volatile`i çok iş parçacıklı bir bellek modeli içinde,
görünürlük ve sıralama garantisi veren bir anahtar sözcük olarak ele alıyordu: bir
iş parçacığının yazdığı değeri başka bir iş parçacığının doğru sırada görmesini
sağlayan, çalışma zamanı ve donanım arasında koordine edilmiş bir sözleşme. Buradaki
`volatile sig_atomic_t`nin verdiği garanti çok daha dar: birden çok çekirdek ya da
iş parçacığı arasında bir görünürlük sırası kurmuyor, yalnız **aynı** akışın, bir
sinyal işleyicisi tarafından kesintiye uğratıldığında, derleyicinin o değişkeni
yazmaçta tutmasını ya da okumasını yeniden sıralamasını engelliyor. C standardı bu
türü çok işlemcili bellek sıralaması için tanımlamıyor; POSIX bunu yalnız aynı süreç
içindeki senkron olmayan bir kesinti için garanti ediyor. İki dilin aynı anahtar
sözcüğü, iki farklı sorunu çözüyor: biri tek akışlı bir kesinti sınırını, öbürü
çok çekirdekli bir donanımın bellek tutarlılık sınırını aşıyor.

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

Bu ölçüm standart sinyalleri **saymaz**. POSIX standart sinyalleri için bekletilmiş
durum bir bayraktır, bir sayaç değildir: aynı sinyal engelliyken kaç kez gönderilirse
gönderilsin, açılışta yalnız bir teslim gerçekleşir. `sinyal_olcumu.c` bunu tam
olarak gösteriyor — iki `raise` çağrısına karşılık `teslim_sayisi` yalnız `1`e
çıkıyor. Gerçek zamanlı sinyaller (`SIGRTMIN` ve üzeri) farklı bir kuraldadır ve
birikerek kuyruğa alınabilir, ama bu ders standart sinyallerle sınırlı ve o farkı
konu edinmiyor.

Bu sınır, ölçümün neyi doğrulayabileceğini de belirliyor: `sinyal_olcumu.c`,
engellenmiş süre boyunca kaç kez `raise` çağrıldığını değil, yalnız açılıştan sonra
kaç kez teslim edildiğini sayıyor. İki `raise` çağrısı tek bir teslime düşse bile,
bu bir kayıp değil — çekirdeğin standart sinyaller için verdiği garanti zaten "en az
bir kez", "her çağrı için bir kez" değil. Ders bu garantiyi aşan bir sayım yapmıyor.

Bu ders ayrıca `signal()` işlevini değil, `sigaction`ı kullanıyor; bu bir stil
tercihi değil. Eski `signal()` arayüzünün davranışı sistemler arasında farklılaşır
— bazı eski Unix türevlerinde işleyici çağrıldıktan sonra tekrar varsayılana
döner, bazılarında dönmez — ve bu belirsizlik yüzünden taşınabilir kod yazmak
ister. `sigaction`, işleyicinin kalıcılığını, hangi ek sinyallerin işleyici
sırasında engelleneceğini (`sa_mask`) ve kesintiye uğrayan sistem çağrılarının
yeniden başlatılıp başlatılmayacağını (`SA_RESTART`) açıkça belirtme imkânı
veriyor. Bu ders `SA_RESTART`ı ayarlamıyor; bu, engelleyici bir sistem çağrısı
sinyal tarafından kesintiye uğrarsa çağrının `EINTR` hatasıyla dönebileceği
anlamına gelir, ama `sinyal_olcumu.c` böyle bir çağrı içermediği için bu davranış
bu derste gözlenmiyor — yalnız ileride karşılaşılacak bir sınır olarak not
düşülüyor.

#### Özet

- Bir sinyal işleyicisi ana akışın dışında, beklenmedik bir noktada çalışır; bıraktığı
  iz ana akışa aynı süreç içinde geri döner.
- `volatile sig_atomic_t`, işleyici ile ana akış arasında güvenli tek paylaşım
  biçimidir; derleyicinin yeniden sıralamasını ve yarım okuma-yazmayı engeller.
- Bu dersin sınıfı **aktarım**: işleyicinin kaldırdığı bayrak ana akışa ulaşıyor, ama
  bir kayıt gibi sürekli paylaşılmıyor — bir kez taşınıyor.
- `sigpending` ile bekletilmiş durum doğrudan gözlemlenebilir: maskeliyken kümede
  tutulan bilgi tek bir bit, "bekleyen var mı" — kaç kez gönderildiği değil; bu yüzden
  engellenmiş bir sinyal birden çok kez gönderilse bile teslim maske kalkınca
  yalnız bir kez gerçekleşir.
- İşleyici içinde eşzamansız-sinyal-güvenli olmayan bir çağrı ya da `volatile`
  olmayan bir paylaşım tanımsız davranıştır; bu ders ikisini de koşturmuyor.
- C'deki `volatile sig_atomic_t` ile Java'daki `volatile` aynı sözcüğü taşır ama
  farklı sorunları çözer: biri tek akışlı kesinti güvenliği, öbürü çok iş parçacıklı
  görünürlük garantisidir.

#### Sonraki Adım

Bir sonraki ders aktarım sınıfını sürdürüyor, ama farklı bir kanaldan: bir borunun
taşıdığı baytlar, çocuktan ebeveyne bir kez akan ve ikinci okumada dosya sonuyla
biten bir aktarım. Ortak nokta şu: sinyalde taşınan iz tek bir bit ya da tek bir
sayaç artışıydı, boruda taşınan iz ise değişken uzunlukta bir bayt dizisi olacak —
ikisi de aynı "bir kez, geri dönüşsüz" aktarım kuralına uyuyor, ama taşıdıkları
bilginin biçimi ve miktarı kökten farklı.

### Süreçler Arası İletişim

Source: https://academia.sh/tr/kurslar/c-sistem-programlama/surecler-arasi-iletisim

Bir boru baytları bir kez tasir ve okunan her bayt akistan duser; mmap ile MAP_SHARED kurulan bir eslem ise ayni sayfayi iki surece birden gosterir ve bu ikisi ayni derste iki farkli sinifa duser.

Önceki ders bir sinyalin bıraktığı izi ölçtü: bir bayrak, ana akışa bir kez ulaşan bir
aktarım. Bu ders aynı sınıfı iki süreç arasında ölçüyor, ve karşısına kasıtlı olarak
farklı bir sınıf koyuyor. Bir **boru**, çocuğun yazdığı baytları ebeveyne taşır — okunan
her bayt akıştan düşer, bu **aktarım**dır. Bir **`mmap` paylaşımı** ise aynı bellek
sayfasını iki sürece birden gösterir — biri yazdığında öteki aynı anda görür, bu
**ortak**tır. Aynı derste iki sınıfın yan yana durması, aralarındaki farkı görünür
kılıyor.

#### Boruya Yazılan Değer

`gozlem.h` ve `gozlem.c` değişmiyor. Bu dersin kendi dosyası `iletisim_olcumu.c`, dört
ölçümü sırayla koşturuyor: boru, paylaşılan eşlem, iki yönlü bir kanal, ve uzunluk
önekiyle ayrılan iki ileti.

```c
// gozlem.h — bir degisikligin kime ulastigini siniflayan kayit
#ifndef GOZLEM_H
#define GOZLEM_H

enum Sinif { KOPYA, ORTAK, AKTARIM, GOZLENMEYEN };

/* Bir degisikligi kaydeder: gordu, degisikligi yapmayan tarafin onu
   okuyup okuyamadigidir (1 ya da 0). */
void gozlem(const char *degisiklik, enum Sinif sinif, int gordu);

/* Sinif basina sayilari ve ulasan / ulasmayan toplamini basar. */
void gozlem_ozeti(void);

#endif
```

```c
// gozlem.c — sayilar yalniz bu birimde tutulur
#include <stdio.h>
#include "gozlem.h"

static const char *const adlar[] = { "kopya", "ortak", "aktarim", "gozlenmeyen" };
static int sayilar[4];
static int ulasan = 0;
static int ulasmayan = 0;

void gozlem(const char *degisiklik, enum Sinif sinif, int gordu) {
    /* ortak ve aktarim ulasir, kopya ve gozlenmeyen ulasmaz */
    int beklenen = (sinif == ORTAK || sinif == AKTARIM);
    sayilar[sinif]++;
    if (gordu) {
        ulasan++;
    } else {
        ulasmayan++;
    }
    printf("%-34s %-8s %s%s\n", degisiklik, gordu ? "gordu" : "gormedi",
           adlar[sinif], gordu == beklenen ? "" : "  TUTARSIZ");
}

void gozlem_ozeti(void) {
    printf("siniflar: kopya %d, ortak %d, aktarim %d, gozlenmeyen %d\n",
           sayilar[KOPYA], sayilar[ORTAK], sayilar[AKTARIM], sayilar[GOZLENMEYEN]);
    printf("ulasan %d, ulasmayan %d\n", ulasan, ulasmayan);
}
```

```c
// iletisim_olcumu.c — boru, paylasilan eslem ve iki yonlu kanal
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include "gozlem.h"

/* 1. Boru: cocugun yazdigi deger ebeveyne bir kez ulasir, ikinci okuma dosya sonu verir */
static void boru_olcumu(void) {
    int uc[2];
    if (pipe(uc) != 0) {
        return;
    }
    fflush(stdout);
    pid_t p = fork();
    if (p == 0) {
        int deger = 42;
        close(uc[0]);
        ssize_t n = write(uc[1], &deger, sizeof deger);
        close(uc[1]);
        _exit(n == (ssize_t)sizeof deger ? 0 : 1);
    }
    close(uc[1]);
    int okunan = 0;
    ssize_t birinci = read(uc[0], &okunan, sizeof okunan);
    waitpid(p, NULL, 0);
    char kalan;
    ssize_t ikinci = read(uc[0], &kalan, 1);
    close(uc[0]);
    gozlem("boruya yazilan deger", AKTARIM,
           birinci == (ssize_t)sizeof okunan && okunan == 42 && ikinci == 0);
}

/* 2. Paylasilan eslem: cocugun yazdigi sayfayi ebeveyn ayni anda gorur */
static void eslem_olcumu(void) {
    int *pay = mmap(NULL, sizeof *pay, PROT_READ | PROT_WRITE,
                     MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (pay == MAP_FAILED) {
        return;
    }
    *pay = 0;
    fflush(stdout);
    pid_t p = fork();
    if (p == 0) {
        *pay = 99;
        _exit(0);
    }
    waitpid(p, NULL, 0);
    gozlem("cocugun yazdigi paylasilan deger", ORTAK, *pay == 99);
    munmap(pay, sizeof *pay);
}

/* 3. Iki yonlu kanal: bir uctan yazilan deger oteki uctan okunur */
static void kanal_olcumu(void) {
    int uc[2];
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, uc) != 0) {
        return;
    }
    fflush(stdout);
    pid_t p = fork();
    if (p == 0) {
        close(uc[0]);
        int deger = 7;
        write(uc[1], &deger, sizeof deger);
        int yanit = 0;
        read(uc[1], &yanit, sizeof yanit);
        _exit(yanit == 14 ? 0 : 1);
    }
    close(uc[1]);
    int gelen = 0;
    read(uc[0], &gelen, sizeof gelen);
    int yanit = gelen * 2;
    write(uc[0], &yanit, sizeof yanit);
    int durum = 0;
    waitpid(p, &durum, 0);
    close(uc[0]);
    gozlem("kanaldan gelen yanit", AKTARIM,
           gelen == 7 && WIFEXITED(durum) && WEXITSTATUS(durum) == 0);
}

/* 4. Uzunluk onekiyle ileti siniri: bir borudan gelen iki mesaj birbirine karismadan ayrilir */
static ssize_t tam_oku(int fd, void *arabellek, size_t sayi) {
    size_t okunan = 0;
    while (okunan < sayi) {
        ssize_t n = read(fd, (char *)arabellek + okunan, sayi - okunan);
        if (n <= 0) {
            return n;
        }
        okunan += (size_t)n;
    }
    return (ssize_t)okunan;
}

static void onekli_yaz(int fd, const char *metin) {
    unsigned char uzunluk = (unsigned char)strlen(metin);
    write(fd, &uzunluk, 1);
    write(fd, metin, uzunluk);
}

static void onek_olcumu(void) {
    int uc[2];
    if (pipe(uc) != 0) {
        return;
    }
    fflush(stdout);
    pid_t p = fork();
    if (p == 0) {
        close(uc[0]);
        onekli_yaz(uc[1], "ilk");
        onekli_yaz(uc[1], "ikincimesaj");
        close(uc[1]);
        _exit(0);
    }
    close(uc[1]);

    unsigned char uzunluk1 = 0;
    tam_oku(uc[0], &uzunluk1, 1);
    char birinci[32];
    tam_oku(uc[0], birinci, uzunluk1);
    birinci[uzunluk1] = '\0';

    unsigned char uzunluk2 = 0;
    tam_oku(uc[0], &uzunluk2, 1);
    char ikinci[32];
    tam_oku(uc[0], ikinci, uzunluk2);
    ikinci[uzunluk2] = '\0';

    waitpid(p, NULL, 0);
    close(uc[0]);
    gozlem("onekli iki mesajin dogru ayrilmasi", AKTARIM,
           strcmp(birinci, "ilk") == 0 && strcmp(ikinci, "ikincimesaj") == 0);
}

int main(void) {
    boru_olcumu();
    eslem_olcumu();
    kanal_olcumu();
    onek_olcumu();
    gozlem_ozeti();
    return 0;
}
```

```text
boruya yazilan deger               gordu    aktarim
cocugun yazdigi paylasilan deger   gordu    ortak
kanaldan gelen yanit               gordu    aktarim
onekli iki mesajin dogru ayrilmasi gordu    aktarim
siniflar: kopya 0, ortak 1, aktarim 3, gozlenmeyen 0
ulasan 4, ulasmayan 0
```

**SA12.** Dört satır da `gordu` diyor, ama ikinci satırın sınıfı ötekilerden farklı.
Çocuk süreç boruya `42` yazıyor, ebeveyn `read` ile bunu okuyor — bu bir **aktarım**:
bayt akıştan düşüyor, ikinci `read` çağrısı artık okunacak bir şey kalmadığı için `0`
dönüyor, yani dosya sonu. Bu `0` dönüşü bir hata değil, bir **durum**dur: `read`in
dönüş değeri normalde okunan bayt sayısıdır, negatifse hata, sıfırsa "yazma ucu
kapandı ve arabellekte artık okunacak veri yok" demektir. `boru_olcumu` çocuk tarafında
`close(uc[1])` çağırıyor, sonra `_exit` ediyor — yazma ucunun son kopyası da kapanınca
çekirdek okuma ucuna "artık yeni veri gelmeyecek" bilgisini işte bu `0` dönüşüyle
taşıyor. Eğer çocuk yazma ucunu kapatmasaydı, ikinci `read` veri gelene kadar
bloke olurdu — `0` dönüşü veri yokluğunun değil, **tüm yazıcıların ayrıldığının**
işareti.

Ama `eslem_olcumu` farklı çalışıyor: `mmap(..., MAP_SHARED |
MAP_ANONYMOUS, ...)` çağrısı `fork`tan önce bir bellek sayfası ayırıyor ve bu sayfayı
**paylaşılan** olarak işaretliyor. `fork` süreç belleğini kopyalarken bu özel eşlemi
kopyalamıyor — ebeveyn ve çocuk, aynı fiziksel sayfayı gösteren iki ayrı işaretçiye
sahip oluyor. Çocuk `*pay = 99` yazdığında, ebeveyn `waitpid`den sonra kendi
`*pay`ına baktığında da `99` görüyor — çünkü ikisi de aynı sayfaya bakıyor. Bu **ortak**
sınıfın tanımı: bir kayıt gibi bir kez taşınmıyor, sürekli paylaşılıyor. Burada "dosya
sonu" kavramının hiçbir karşılığı yok: bir bellek sayfasının "tükenmesi" diye bir şey
yok, sayfa süreçlerden biri `munmap` edene ya da süreç sonlanana kadar orada duruyor.

#### Boru mu, Paylaşılan Bellek mi

**SA13.** Bu iki düzeneğin görünürlüğü aynı kelimeyle anlatılabilir — "çocuğun yazdığını
ebeveyn görüyor" — ama altındaki mekanizma tamamen farklı. Boru, çekirdeğin tuttuğu bir
arabellektir; yazma ve okuma birer sistem çağrısı, ve okunan veri arabellekten silinir.
İki süreç asla aynı belleğe bakmaz, çekirdek aradaki taşıyıcıdır. `MAP_SHARED` ile
kurulan eşlem ise gerçekten aynı fiziksel sayfayı gösterir; okuma ve yazma sıradan
bellek erişimidir, sistem çağrısı gerekmez, ve veri hiçbir zaman "tükenmez" — sayfa
her okunduğunda orada kalır. Bu yüzden borunun ikinci okuması dosya sonu verirken,
paylaşılan sayfanın ikinci okuması hâlâ aynı değeri verir; `eslem_olcumu` bunu
denemiyor çünkü fark zaten birinci okumada görünüyor.

Bu karşıtlığın pratik sonucu, hangi düzeneğin ne zaman seçileceğidir. Boru sıralı,
tek yönlü bir akış taşıdığı ve çekirdek tarafından senkronize edildiği için
"kimin ne zaman ne gönderdiği" sorusuna doğal bir cevap verir — okuyan taraf
veri gelene kadar bloke olur, yarışa girmez. `MAP_SHARED` ise senkronizasyonu
**vermez**: iki süreç aynı sayfaya aynı anda yazarsa sonuç hangi yazmanın önce
göründüğüne bağlıdır ve bu ders böyle bir çakışmayı kurmuyor, yalnız tek yönlü
bir yazma-sonra-oku sırası kullanıyor. Paylaşılan bellek, sırayı değil yalnız
görünürlüğü veriyor — kilitleme ayrı bir sorumluluk, bu konunun son
dersinde iş parçacıkları için ayrıca ele alınıyor.

`kanal_olcumu`, `socketpair` ile kurulan iki yönlü bir kanalı bir kez gösteriyor. Bu
kanal da bir boru gibi çekirdek üzerinden akan, tükenen bir taşıyıcı — aradaki fark
tek yönlü değil çift yönlü olması: aynı iki uçtan hem yazılabiliyor hem okunabiliyor.
Ders burada ağ soketi açmıyor; `AF_UNIX` yerel bir uç noktadır, adres ya da bağlantı
noktası hiç yazdırılmıyor. Çocuk bir değer gönderiyor, ebeveyn onu ikiyle çarpıp geri
yolluyor, çocuk yanıtı okuyup çıkış koduna yazıyor — bu da **aktarım**, çünkü kanal
üzerinden akan her değer bir kez okunup tüketiliyor. `pipe` ile `socketpair` arasındaki
fark yön sayısıdır, sınıf değil: `pipe` tek yönlü bir dosya tanıtıcı çifti verir,
`socketpair` her iki tanıtıcıdan da hem okunup hem yazılabilen tam çift yönlü bir
kanal verir — ikisi de aynı çekirdek arabellekleme kuralına, aynı tükenme davranışına
tabidir.

Bu tabiiyetin somut bir sonucu var: `socketpair`ın iki ucu birbirinden bağımsız iki
tanıtıcı gibi görünse de, her iki tanıtıcının da yazma tarafı kapanmadan öteki uç
dosya sonu görmez — tıpkı `pipe`da olduğu gibi. Fark yalnızca kimin kime yazabildiği:
`pipe`da bir uç yalnız yazabilir, öteki yalnız okuyabilir; `socketpair`da her iki
uç da her iki yönde kullanılabilir. Bu yüzden `kanal_olcumu` çocuğun kanalı hangi
sırayla kapattığı konusunda dikkatli olmalı — kendi tanıtıcısını yanıtı gönderdikten
sonra kapatmazsa, ebeveyn tarafındaki okuma sonsuza kadar bekleyebilirdi, çünkü
çekirdek hâlâ açık bir yazma ucu görür ve dosya sonu üretmez. `onek_olcumu`daki
`onekli_yaz` çağrılarının ardından `close(uc[1])` çağrılması da aynı kuralın bir
başka görünümü: ebeveyn tarafı iki iletiyi okuduktan sonra beklemeye devam etmiyor,
çünkü çocuk kendi yazma ucunu açıkça kapatıyor.

Bu üç düzenek arasındaki seçim, veri akışının şekline bağlı. Tek yönlü ve sıralı
bir akış için `pipe` en basit araç; hem gönderme hem alma yönü aynı iki uçtan
gerekiyorsa `socketpair` fazladan bir kanal açmayı önlüyor; büyük ya da sık
paylaşılan veri için kopyalama maliyetini tamamen ortadan kaldırmak gerekiyorsa
`MAP_SHARED` devreye giriyor, ama o zaman senkronizasyon sorumluluğu tasarımcıya
kalıyor.

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

**SA14.** Bir boru bir **akıştır**, ileti sınırı taşımaz. Önceki üç ölçümdeki her
`write` çağrısı tek bir `read` ile karşılandı, ama bu garanti genel değil: iki ayrı
`write` çağrısı bir tek `read` ile birlikte gelebilir, ya da bir `write`ın verisi
birden çok `read` çağrısına bölünebilir. Boru, gönderilen baytları bir kuyrukta tutar
ve nereye bir iletinin bittiğini, ötekinin başladığını bilmez — bu bilgiyi taşıyan
hiçbir şey yoktur.

**SA15.** `onek_olcumu` bu sınırı gerçekten kuruyor ve gerçekten sınıyor. Çocuk süreç aynı
boruya art arda iki ileti yazıyor — `"ilk"` ve `"ikincimesaj"` — ama ham metni değil,
`onekli_yaz` her iletinin önüne tek baytlık bir uzunluk alanı koyuyor. Ebeveyn tarafı
bu iki iletiyi tek bir `read` çağrısıyla değil, `tam_oku` yardımcı işleviyle okuyor:
önce uzunluk baytını, sonra tam olarak o kadar veri baytını. `tam_oku`nun kendisi de
bir garantiyi telafi ediyor — tek bir `read` çağrısı istenen baytların hepsini
getirmeyebilir, bu yüzden işlev istenen sayıya ulaşana kadar döngüde okumaya devam
ediyor. İki iletinin baytları aynı boruda art arda, aralarında hiçbir ayraç olmadan
akıyor; okuyan tarafı doğru yerden kesen tek şey, her iletinin kendi taşıdığı uzunluk
bilgisi. Ölçüm `"ilk"` ve `"ikincimesaj"`ın karışmadan, doğru sınırlarla geri
kurulduğunu doğruluyor — bu ders üç iletiyi tek bir okumada gösterdiği için kendi
başına bir ileti sınırı çizmiyordu, ama `onek_olcumu` artık çiziyor: basılan yalnız
yeniden kurulmuş iletiler, boruya ham baytlar olarak değil.

`onekli_yaz` uzunluğu `unsigned char` olarak taşıyor, yani tek baytlık bir alan —
en çok 255 baytlık bir iletiyi tanımlayabiliyor. Bu ölçüm için yeterli, çünkü
`"ikincimesaj"` bu sınırın çok altında; ama gerçek bir protokolde ileti büyüklüğü
öngörülemezse tek bayt yetmez, iki ya da dört baytlık bir alan gerekir ve o zaman
bayt sırası (`endianness`) sorusu araya girer — gönderen ve alan makine aynı sırayla
mı yazıp okuyor. Bu ders o soruyu açmıyor, çünkü boru her iki ucu da aynı makinede
çalıştırıyor; ağ üzerinden konuşan bir protokolde uzunluk alanının sırası açıkça
sabitlenmeli, aksi hâlde bir uçta doğru okunan sayı öteki uçta yanlış çıkar.
Önemli olan kural değişmiyor: uzunluk alanı ne kadar büyük olursa olsun, onu taşıyan
düzenek aynı — önce ne kadar veri geleceğini oku, sonra tam o kadarını oku.

`tam_oku`nun döngüsü kozmetik değil, boru semantiğinin doğrudan sonucu. Çekirdek
bir `write` çağrısındaki baytları tek bir birim olarak teslim etmeyi garanti etmez;
okuyan taraf `read` çağırdığında çekirdek o an boru arabelleğinde ne kadar bayt
birikmişse onu döndürür, istenenden azını döndürebilir. `boru_olcumu` ve
`eslem_olcumu` ölçümlerinde bu hiç görünmedi, çünkü yazılan değerler birkaç bayt
uzunluğundaydı ve tek `read` çağrısı hep yetti; ama genel kural bu değil. `tam_oku`
istenen sayıya ulaşılana kadar okumaya devam ederek bu garantisizliği bir kez, tek
bir yerde telafi ediyor — çağıran taraf artık "kaç kez `read` gerekti" sorusuyla
uğraşmıyor, `tam_oku`nun kendisi kısa okumaları görünmez kılıyor. `n <= 0` dönen
anı ayrıca kontrol ediyor, çünkü `0` boru kapandığını, negatif değer ise bir hatayı
gösteriyor; ikisi de "tam sayıda bayt bekleniyordu ama gelmedi" durumudur ve
döngü bu durumda sonsuza kadar dönmek yerine hemen çıkıyor.

#### Özet

- Bu ders iki farklı sınıfı yan yana koyuyor: boru **aktarım**dır, `MAP_SHARED` eşlemi
  **ortak**tır — ikisi de "çocuğun yazdığını ebeveyn görür" cümlesiyle anlatılsa da
  mekanizmaları ayrı.
- Boru çekirdek üzerinden akan, okunduğunda tükenen bir taşıyıcıdır; ikinci okuma
  `0` bayt döner, çünkü tüm yazma uçları kapanmıştır — bu veri yokluğu değil,
  yazıcıların ayrıldığının işaretidir.
- `MAP_SHARED | MAP_ANONYMOUS` ile kurulan bir eşlem aynı fiziksel sayfayı iki sürece
  birden gösterir; bir taraf yazdığında öteki taraf aynı sayfayı okur, veri tükenmez —
  ama sıralamayı değil yalnız görünürlüğü verir, kilitleme ayrı bir sorumluluktur.
- `socketpair` iki yönlü bir kanal kurar; bu da bir aktarımdır, çünkü akan her değer
  bir kez okunup tüketilir. Ağ soketi açılmaz, adres ya da bağlantı noktası basılmaz.
- Bir boru ileti sınırı taşımaz; `onek_olcumu` bunu uzunluk önekiyle gerçekten kurup
  iki iletiyi karıştırmadan geri kurarak gösteriyor.

#### Sonraki Adım

Bir sonraki ders — bu konunun sonuncusu — aynı süreç içinde kalıyor ama süreç yerine iş
parçacığına geçiyor: kilit altında yazılan bir sayaç, `join` sonrasında ana iş
parçacığına ulaşan bir **ortak** paylaşım.

### POSIX İş Parçacıkları

Source: https://academia.sh/tr/kurslar/c-sistem-programlama/posix-is-parcaciklari

Ayni surec icindeki iki is parciciginin ayni bellegi kilit altinda degistirmesi, borunun tasidigi baytlar gibi bir kez tuketilen bir aktarim degil, surekli paylasilan bir ortak degisimdir.

Sistem arayüzlerinin son dersi aynı süreç içinde kalıyor, ama süreç sınırını aşan bir taşıma
biçiminden vazgeçip aynı bellek uzayını paylaşan iki akışa geçiyor: **iş parçacığı**.
`fork` bir kopya verdi, sinyal bir aktarım bıraktı, boru ve `mmap` süreçler arası
sınırı iki farklı biçimde geçti. İki iş parçacığı ise hiç sınır geçmiyor — aynı
sürecin aynı belleğini baştan paylaşıyor. Bir kilit altında yapılan bir değişiklik bu
yüzden kursun dördüncü sınıfına düşüyor: **ortak**.

`pthread_create` bunu nasıl kurduğuyla da `fork`tan ayrılıyor. `fork` çağrıldığı anda
çekirdek belleğin sayfa tablosunu kopyalıyor — kopyala-yaz (`copy-on-write`) sayesinde
fiziksel sayfalar hemen çoğaltılmıyor, ama iki süreç artık ayrı adres uzaylarına
sahip, biri yazınca öteki görmüyor. `pthread_create` ise yeni bir adres uzayı açmıyor;
yalnız yeni bir yığıt (`stack`) ayırıp aynı sürecin içinde ikinci bir yürütme noktası
başlatıyor. Kod bölütü, veri bölütü, öbek (`heap`) — hepsi tek bir kopya, iki akış
aynı adresleri görüyor. Bu yüzden `sayac` gibi bir küresel değişken iki iş parçacığı
için de aynı bellek konumu; `fork` sonrası aynı isimli bir değişkenin iki bağımsız
kopyası olmasıyla karşılaştırıldığında, bu fark bu dersin sınıfını doğrudan belirliyor.

#### Kilit Altında Yazılan Sayaç

`gozlem.h` ve `gozlem.c` değişmiyor. Bu dersin kendi dosyası `parcacik_olcumu.c`, iki
iş parçacığının aynı sayacı kilit altında artırdığı ve sonucun tam olarak beklenen
değere ulaştığı tek bir ölçümü koşturuyor.

```c
// gozlem.h — bir degisikligin kime ulastigini siniflayan kayit
#ifndef GOZLEM_H
#define GOZLEM_H

enum Sinif { KOPYA, ORTAK, AKTARIM, GOZLENMEYEN };

/* Bir degisikligi kaydeder: gordu, degisikligi yapmayan tarafin onu
   okuyup okuyamadigidir (1 ya da 0). */
void gozlem(const char *degisiklik, enum Sinif sinif, int gordu);

/* Sinif basina sayilari ve ulasan / ulasmayan toplamini basar. */
void gozlem_ozeti(void);

#endif
```

```c
// gozlem.c — sayilar yalniz bu birimde tutulur
#include <stdio.h>
#include "gozlem.h"

static const char *const adlar[] = { "kopya", "ortak", "aktarim", "gozlenmeyen" };
static int sayilar[4];
static int ulasan = 0;
static int ulasmayan = 0;

void gozlem(const char *degisiklik, enum Sinif sinif, int gordu) {
    /* ortak ve aktarim ulasir, kopya ve gozlenmeyen ulasmaz */
    int beklenen = (sinif == ORTAK || sinif == AKTARIM);
    sayilar[sinif]++;
    if (gordu) {
        ulasan++;
    } else {
        ulasmayan++;
    }
    printf("%-34s %-8s %s%s\n", degisiklik, gordu ? "gordu" : "gormedi",
           adlar[sinif], gordu == beklenen ? "" : "  TUTARSIZ");
}

void gozlem_ozeti(void) {
    printf("siniflar: kopya %d, ortak %d, aktarim %d, gozlenmeyen %d\n",
           sayilar[KOPYA], sayilar[ORTAK], sayilar[AKTARIM], sayilar[GOZLENMEYEN]);
    printf("ulasan %d, ulasmayan %d\n", ulasan, ulasmayan);
}
```

```c
// parcacik_olcumu.c — iki is parciciginin kilit altinda ayni sayaci artirmasi
#define _POSIX_C_SOURCE 200809L
#include <pthread.h>
#include <stdio.h>
#include "gozlem.h"

#define ADIM 1000

static long sayac = 0;
static pthread_mutex_t kilit = PTHREAD_MUTEX_INITIALIZER;

static void *artir(void *veri) {
    (void)veri;
    for (int i = 0; i < ADIM; i++) {
        pthread_mutex_lock(&kilit);
        sayac = sayac + 1;
        pthread_mutex_unlock(&kilit);
    }
    return NULL;
}

int main(void) {
    pthread_t p1, p2;

    pthread_create(&p1, NULL, artir, NULL);
    pthread_create(&p2, NULL, artir, NULL);

    pthread_join(p1, NULL);
    pthread_join(p2, NULL);

    gozlem("iki is parciciginin kilit altinda yazdigi toplam", ORTAK,
           sayac == 2L * ADIM);

    pthread_mutex_destroy(&kilit);
    gozlem_ozeti();
    return 0;
}
```

```text
iki is parciciginin kilit altinda yazdigi toplam gordu    ortak
siniflar: kopya 0, ortak 1, aktarim 0, gozlenmeyen 0
ulasan 1, ulasmayan 0
```

**SA16.** Tek satır `gordu` diyor, ve bu sonuç şans eseri değil, garanti edilmiş. İki
iş parçacığı da `artir` işlevini koşturuyor, her biri `sayac`ı bin kez artırıyor;
ana iş parçacığı her iki `pthread_join` çağrısı döndükten sonra `sayac`a bakıyor ve
tam olarak `2000` buluyor. Bu, `fork`un verdiği bağımsız kopyalardan temelde farklı:
`pthread_create` yeni bir bellek kopyası açmıyor, aynı sürecin aynı adres uzayında
yeni bir yürütme akışı başlatıyor. `sayac` değişkeni tek bir yerde duruyor, ve iki
akış da o tek yere yazıyor — bu yüzden sınıf **ortak**, `fork`taki **kopya** değil.
`ADIM` sabiti bin olarak seçildiği için toplam da kesin: iki iş parçacığı toplam
`2 * 1000 = 2000` artırma yapıyor, ve kilit hiçbirini kaybettirmiyorsa sonuç yalnız
bu sayı olabilir — ara sıra `1998` ya da `2001` çıkan bir ölçüm bu dersin kabul
ettiği bir sonuç değil, çünkü kursun ölçüm yöntemi yalnız deterministik, tekrar
tekrar aynı çıkan sonuçları doğruluyor.

`sayac = sayac + 1` satırı tek bir C ifadesi gibi görünse de üç ayrı adıma karşılık
gelir: belleğe git, değeri oku, bir ekle, sonucu geri belleğe yaz. Kilit olmadan bu
üç adım kesintiye açıktır — bir iş parçacığı okuma ile yazma arasındayken öteki
iş parçacığı da aynı eski değeri okuyabilir, ikisi de kendi hesapladığı `+1`i geri
yazar ve iki artırımdan biri kaybolur. `pthread_mutex_lock` bu üç adımı bölünmez
bir birim hâline getirir: kilidi tutan iş parçacığı üçünü de tamamlamadan öteki
iş parçacığı kilidi alamaz, bu yüzden hiçbir artırım kayıp gitmez. `ADIM` değerinin
büyüklüğü de kasıtlı — yeterince küçük bir döngü sayısında kilitsiz bir sürüm bile
şans eseri doğru sonuç verebilir, çünkü iki iş parçacığının adımları çakışmadan
sırayla ilerleyebilir; bin adımlık bir döngü bu şansı pratikte sıfıra indiriyor,
ama kursun metni bunu hiç koşturmadan, yalnız düzyazıyla anlatıyor.

Java'nın standart koleksiyonlarında bu sorunun farklı bir yüzü var: bir `ArrayList`
ya da `HashMap` üzerinde gezinirken (`iterator`) koleksiyon başka bir iş parçacığınca
değiştirilirse, çoğu gerçekleştirim veriyi sessizce bozmak yerine
`ConcurrentModificationException` fırlatır — bu "hızlı-başarısız" (`fail-fast`)
davranış, M08/K03'ün "Yineleyiciler ve Eşzamanlı Değişiklik" konusunun kapsamı.
`parcacik_olcumu.c`'deki `sayac` ise tekil bir `long`, gezinilen bir yapı değil;
kilit onu bozulmadan tutuyor, ama C'nin kendisinde Java'nınkine benzer bir çalışma
zamanı denetimi yok — kilidi unutup `sayac`a doğrudan yazan bir kod, hatasız
derlenir ve sessizce yanlış sonuç üretir, `ConcurrentModificationException` gibi bir
uyarı vermez. Denetim burada derleyicide ya da çalışma zamanında değil, tamamen
programcının disiplininde.

#### Kilidin Garanti Ettiği: Görünürlük, Sıralama Değil

**SA17.** `pthread_mutex_lock` ve `pthread_mutex_unlock` çifti `sayac = sayac + 1` satırını
sarıyor, ve bu sarmalama iki ayrı şeyi birden garanti ediyor. Birincisi **karşılıklı
dışlama**: iki iş parçacığı aynı anda kilidi tutamaz, bu yüzden `sayac`ı okuyup bir
artırıp geri yazma adımı hiçbir zaman yarım kesilmez — biri bu üç adımı tamamlamadan
öteki başlayamaz. İkincisi **görünürlük**: bir iş parçacığı kilidi bıraktığında,
yaptığı değişiklik kilidi bir sonra alan iş parçacığına garantili olarak görünür.
Kilit olmadan bu iki garanti de kaybolur — iki iş parçacığı aynı `sayac`ı aynı anda
okuyup aynı eski değere bir ekleyebilir, ve bir artırma kaybolabilir. `parcacik_olcumu.c`
bu kayıp durumu hiç koşturmuyor; kilitsiz bir sürüm tarif ediliyor ama çalıştırılmıyor,
çünkü sonucu deterministik değil, ve bu kursun ölçüm yöntemi yalnız deterministik
sonuçları doğruluyor.

Kilit görünürlüğü garanti ederken **hangi iş parçacığının önce çalıştığını** garanti
etmiyor. `p1` ve `p2` hangi sırayla zamanlanırsa zamanlansın, hangisinin kilidi kaç
kez önce aldığı `parcacik_olcumu.c` içinde hiç ölçülmüyor ve hiç basılmıyor — yalnız
nihai toplam ölçülüyor. Bu, kursun baştan koyduğu kurala da uyuyor: iş parçacığı
kimliği hiçbir zaman yazdırılmıyor, ve iki akış da kendi aralarında yazdırma yapmıyor,
yalnız ana iş parçacığı her iki `join` döndükten sonra tek bir satır basıyor.

Java Bellek Modeli'nde (JMM) benzer bir görünürlük garantisi `volatile` anahtar
sözcüğüyle ve senkronize bloklarla kuruluyor — M08/K04'ün "JVM, Eşzamanlılık ve
Başarım" konusu bunu "önce-olur" (`happens-before`) ilişkisiyle tanımlıyor: bir
kilidi bırakan iş parçacığının o kilitten önceki tüm yazmaları, kilidi bir sonra
alan iş parçacığına görünür olur. Kural kelime kelime `pthread_mutex_unlock`/
`pthread_mutex_lock` çiftinin verdiği garantiyle örtüşüyor; fark, JMM'nin bunu
sanal makinenin bellek sıralaması üzerinden **dilin kendi belirtiminde** tanımlamış
olması, C'de ise bu garantinin POSIX iş parçacığı belirtiminin çağrı sözleşmesinden
geldiği — derleyici ve donanım sıralamasını `pthread_mutex_lock`/`unlock`
çağrılarının kendisi kısıtlıyor, dilin bir bellek modeli olarak ayrı bir kavramı yok.

Go'nun eşzamanlılık modeli aynı sorunu tersten çözüyor. M09/K03'ün "Goroutine ve
Kanallar" konusundaki ilke — "iletişimle bellek paylaş, belleği paylaşarak iletişim
kurma" — bir değeri bir kanaldan geçirmeyi, o değerin sahipliğini göndericiden
alıcıya devretmek olarak tanımlıyor; iki goroutine aynı değişkene aynı anda
yazmıyor, değer bir kanal üzerinden bir kez akıyor. `parcacik_olcumu.c`'deki
`sayac` ise tam tersi: sahiplik hiç devredilmiyor, iki iş parçacığı da aynı
konuma aynı anda erişme hakkına sahip ve bu hakkı `pthread_mutex_t` ile
sınırlıyor. Go'da kanal deseni seçilseydi `sayac`ı paylaşmak yerine her
goroutine kendi kısmi toplamını hesaplayıp bir kanaldan gönderir, toplama
adımı tek bir alıcıda yapılırdı — bu kursun ölçtüğü **ortak** sınıfı değil,
`boru`nun taşıdığına benzer bir **aktarım** deseni olurdu.

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

**SA18.** Bu ölçüm yalnız **nihai değeri** doğruluyor, ara durumu değil. `sayac`ın kilit
altındayken hangi ara değerlerden geçtiği — `1`, `2`, `3` sırasıyla mi yoksa iki iş
parçacığının adımları nasıl örülerek mi ilerlediği — hiçbir yerde ölçülmüyor, çünkü
ölçmenin kendisi ek bir kilitli okuma gerektirir ve bu da ölçülen sistemi değiştirir.
`parcacik_olcumu.c`'nin doğruladığı tek şey, `2000` adımın hiçbirinin kaybolmadığı;
hangi adımın hangi iş parçacığından geldiği sorusu bu dersin kapsamı dışında. Bu sınır
kasıtlı: kurs, iki akışın birbirini görüp görmediğini ölçüyor, aralarındaki yarışı
yeniden kurmuyor. Bir yarış durumunun kendisini gözlemlemek isteyen bir ölçüm —
örneğin hangi iş parçacığının kilidi kaçıncı sırada aldığını saymak — zamanlamaya
bağımlı hâle gelir ve bu kursun deterministik-sonuç kuralını bozar; bu yüzden böyle
bir ölçüm burada yer almıyor.

Bu dersin en büyük sınırı, kilidin **doğru kullanıldığını varsaymasında**. `kilit`
değişkeni `artir` içinde her erişimden önce alınıyor, ama bunu zorlayan hiçbir
mekanizma yok — `sayac`ı kilitsiz okuyan bir üçüncü işlev eklense, derleyici hiçbir
uyarı vermez, kod sessizce hatalı olur. Rust'ın eşzamanlılık modeli bu boşluğu tip
sistemine taşıyor: M10/K04'ün "`Send` ve `Sync`" konusu, bir tipin iş parçacıkları
arasında güvenle taşınıp taşınamayacağını (`Send`) ve paylaşılan referans olarak
güvenle kullanılıp kullanılamayacağını (`Sync`) derleme zamanında işaretliyor.
Paylaşılan bir sayaç Rust'ta çıplak bir değişken olarak değil, `Mutex<i64>` gibi bir
sarmalayıcı içinde tutulur, ve bu sarmalayıcıya kilit almadan erişmeye çalışan kod
**derlenmez** — `parcacik_olcumu.c`'deki gibi bir hatayı çalışma zamanına değil
derleme zamanına taşır. C'de böyle bir denetim yok: `pthread_mutex_t kilit`
`sayac`la aynı dosyada duruyor olsa bile, aralarındaki bağ yalnız bu dersin metninde
anlatılan bir kural, derleyicinin bildiği bir kısıt değil. Bu kursun ölçüm yöntemi
de zaten bu boşluğu kapatmıyor — yalnız kilidin doğru kullanıldığı tek bir koşuyu
doğruluyor, kilidin unutulduğu bir sürümü hiç koşturup göstermiyor.

#### Sonraki Adım ve Sınıfların Toplu Görünümü

**SA19.** Bu beş ders boyunca aynı dört sınıf farklı taşıyıcılarla karşımıza çıktı.
`fork` sonrası bir değişken **kopya**ydı — iki taraf bağımsız, hiçbir değişiklik
karşı tarafa ulaşmadı. Bir sinyal işleyicisinin bıraktığı bayrak **aktarım**dı —
ana akışa bir kez ulaştı. Bir borunun taşıdığı bayt da **aktarım**dı, ikinci okuma
dosya sonuyla bitti. `MAP_SHARED` ile kurulan bir eşlem ve bu dersin kilitli sayacı
ise **ortak**tı — bir kayıt sürekli paylaşılıyor, taraflardan biri değiştirdiğinde
öteki de görüyor. Dördüncü sınıf, **gözlenmeyen**, ilk dersin kendi kapsamında
kaldı: gözlemin kendisinin görülmediği durumlar.

Aynı sonucu farklı bir düzenekle üretmek de mümkündü — örneğin `sayac`ı kilit yerine
atomik bir işlemle artırmak, ya da iş parçacığı yerine paylaşılan bellek üzerinde
başka bir eşzamanlama aracı kullanmak. Bu dersin sınırı, yalnız `pthread_mutex_t` ile
kurulan en yaygın deseni ölçmesi; atomik işlemler ve öteki kilit türleri bu kursun
kapsamı dışında kalıyor.

Bu dört sınıf, taşıyıcının kendisinden bağımsız bir soruyu yanıtlıyor: bir değişiklik
yapan taraf dışındaki bir taraf, o değişikliği görebiliyor mu, ve görüyorsa ne zaman,
kaç kez? `fork` sorusuna hayır diyor — kopya, göstermez. Sinyal ve boru evet diyor
ama yalnız bir kez — aktarım, tüketilir. `MAP_SHARED` ve kilitli sayaç evet diyor ve
sürekli — ortak, tüketilmez. İlk dersin gözlem düzeneğinin kendisi ise bu sorunun
dördüncü, çürük bir yanıtı: bir taraf bir şey görüyor ama ölçüm bunu hiç yakalamıyor —
gözlenmeyen. Beş ders boyunca kullanılan `gozlem()` işlevi bu dört yanıtı tek bir
kelimeyle etiketleyip, o etiketin ölçülen `gordu`/`gormedi` değeriyle tutarlı olup
olmadığını kendiliğinden denetledi; hiçbir ders bu denetimi atlamadı ve hiçbir ölçüm
`TUTARSIZ` etiketiyle bitmedi.

Bu sınıflandırma taşıyıcıya değil **görünürlük deseni**ne bakıyor olduğu için, başka
dillerdeki eşzamanlılık araçlarına da uygulanabilir — M08/K04'teki JMM, M09/K03'teki
kanal, M10/K04'teki `Send`/`Sync` hepsi aynı dört soruyu kendi sözdizimleriyle
yanıtlıyor, yalnız garantiyi nerede kurduğu (çalışma zamanı, tip sistemi, dil
belirtimi) değişiyor. C'nin POSIX iş parçacığı ve süreç arayüzü bu garantiyi hep
çağrı sözleşmesinde kuruyor: `fork` çağrısının kendisi kopya sınıfını, `pthread_
mutex_lock`/`unlock` çifti ortak sınıfını, sinyal teslimi ve boru okuması aktarım
sınıfını tanımlıyor. Sistem arayüzleri burada kapanıyor: beş ders, bir değişikliğin bir
sürecin ya da bir iş parçacığının dışına kimin görebileceği sorusunu dört sınıfla
yanıtladı.

#### Özet

- İki iş parçacığı aynı süreç içinde aynı bellek uzayını paylaşır; `fork`un aksine
  ayrı bir kopya açılmaz.
- Bu dersin sınıfı **ortak**: kilit altında yapılan bir değişiklik her iki tarafa
  da görünür, bir kayıt gibi sürekli paylaşılır.
- `pthread_mutex_lock`/`unlock` iki garanti verir: karşılıklı dışlama ve görünürlük.
  Hangi iş parçacığının önce çalıştığını garanti etmez.
- Ölçüm yalnız nihai toplamı doğruluyor; ara durumları ya da iş parçacıkları
  arasındaki sıralamayı ölçmüyor, çünkü bu ölçmenin kendisi zamanlamaya bağımlı
  hâle gelirdi.
- Kilitsiz bir sürüm yalnız düzyazıyla tarif ediliyor, hiç koşturulmuyor; sonucu
  deterministik olmadığı için bu kursun ölçüm yöntemine uymuyor.
- Aynı görünürlük sorusu başka dillerde farklı yerlerde çözülüyor: Java'da JMM'nin
  `happens-before` ilişkisinde, Go'da kanal sahiplik devrinde, Rust'ta `Send`/`Sync`
  tipinde — C'de ise yalnız `pthread_mutex_t` çağrı sözleşmesinde, derleyici desteği
  olmadan.

#### Sonraki Adım

Sistem arayüzleri burada tamamlanıyor. Beş ders boyunca aynı `gozlem()` birimi beş farklı
taşıyıcıyı — kopyalanan bellek, işleyicinin bıraktığı bayrak, boru, paylaşılan sayfa,
kilitli sayaç — aynı dört sınıfla ölçtü; hiçbiri diğerinin yerine geçmedi, çünkü her
biri farklı bir soruyu, "kim ne zaman neyi görüyor" sorusunun farklı bir biçimini
yanıtladı. Bir sonraki ders (Bağlı Yapılar) aynı gözlem birimini süreç ve iş
parçacığı sınırından alıp tek bir veri yapısının içine taşıyor: iki işaretçi aynı
düğümü gösterdiğinde, birinin yaptığı değişikliği öteki görüyor mu?

## Veri Yapıları ve Deyimler

Bağlı yapılar ve takma ad, halka arabellek ve kuyruklar, karma tabloları, işlev tablosuyla nesne benzeri tasarım, ve işletim sistemi kaynaklarını tek çıkış noktasından temizleme deyimleri.

### Bağlı Yapılar

Source: https://academia.sh/tr/kurslar/c-sistem-programlama/bagli-yapilar

Bağlı liste ve ikili arama ağacının C gerçeklemesi; bir düğümü takma adla değiştirmenin ve sığ kopyanın paylaşılan görünürlüğü, derin kopyanın ise onu kestiği ölçümü.

Önceki ders bir belleği **kimin bırakacağını** ölçtü. Bu kurs farklı bir eksenle
başlıyor: bir düğümü değiştirdiğinde bu değişikliği **kimin göreceği**. Aynı bellek
bloğuna iki farklı işaretçi tutulduğunda, birinden yapılan bir değişiklik ötekinden de
görünür — kaynak bunu söylemez, çünkü iki değişkenin bildirimi de aynıdır (`struct Dugum
*`). Bağlı liste bu paylaşımın en açık örneğidir: bir sonraki düğüme giden alan bir
işaretçi taşır ve o işaretçiyi kim tutuyorsa aynı düğümü görür.

Bu ders bağlı listeyi ve ikili arama ağacını sıfırdan kurup dört sınıftan **ortak**'ı ve
**kopya**'yı aynı yapı üzerinde karşılaştırıyor: bir düğüme takma adla erişip değiştirmek
paylaşılan bir görünürlük üretir (ortak); listenin tamamını yeni düğümlerle çoğaltmak bu
paylaşımı keser (kopya). Önceki kursun (**C'de Bellek ve İşaretçiler**) sorduğu "bu düğümü kim
bırakacak" sorusu burada da geçerli — ağacı bıraktığımızda hâlâ o sözleşmeye uyuyoruz —
ama bu dersin ölçtüğü eksen ayrı: sahiplik değil, görünürlük.

#### Bağlı Liste: Ekleme, Dolaşma, Bırakma

Liste tek yönlü ve her ekleme başa yapılıyor. `liste_toplam` bütün düğümleri dolaşıp
değerlerini topluyor, `liste_serbest` her düğümü bırakmadan önce bir sonrakini saklıyor.
`liste_sig_kopyala` yeni bir düğüm ayırmıyor, gelen başı olduğu gibi döndürüyor; bu yüzden
döndürdüğü liste orijinaliyle **aynı düğümleri** paylaşıyor. `liste_derin_kopyala` ise her
düğüm için yeni bir ayırma yapıp değeri kopyalıyor; döndürdüğü liste bağımsız bir bellek
bloğu zincirine sahip.

```c
// dugum.h — tek yonlu bagli liste ve temel islemler
#ifndef DUGUM_H
#define DUGUM_H

struct Dugum {
    int deger;
    struct Dugum *sonraki;
};

struct Dugum *liste_ekle(struct Dugum *bas, int deger);
int liste_toplam(const struct Dugum *bas);
void liste_serbest(struct Dugum *bas);
struct Dugum *liste_sig_kopyala(struct Dugum *bas);
struct Dugum *liste_derin_kopyala(const struct Dugum *bas);

#endif
```

```c
// dugum.c — liste islemlerinin gerceklemesi
#include <stdlib.h>
#include "dugum.h"

struct Dugum *liste_ekle(struct Dugum *bas, int deger) {
    struct Dugum *yeni = malloc(sizeof *yeni);
    if (yeni == NULL) {
        return bas;
    }
    yeni->deger = deger;
    yeni->sonraki = bas;
    return yeni;
}

int liste_toplam(const struct Dugum *bas) {
    int toplam = 0;
    for (const struct Dugum *d = bas; d != NULL; d = d->sonraki) {
        toplam += d->deger;
    }
    return toplam;
}

void liste_serbest(struct Dugum *bas) {
    while (bas != NULL) {
        struct Dugum *sonraki = bas->sonraki;
        free(bas);
        bas = sonraki;
    }
}

struct Dugum *liste_sig_kopyala(struct Dugum *bas) {
    return bas;
}

struct Dugum *liste_derin_kopyala(const struct Dugum *bas) {
    struct Dugum *yeni_bas = NULL;
    struct Dugum *son = NULL;
    for (const struct Dugum *d = bas; d != NULL; d = d->sonraki) {
        struct Dugum *yeni = malloc(sizeof *yeni);
        if (yeni == NULL) {
            liste_serbest(yeni_bas);
            return NULL;
        }
        yeni->deger = d->deger;
        yeni->sonraki = NULL;
        if (son == NULL) {
            yeni_bas = yeni;
        } else {
            son->sonraki = yeni;
        }
        son = yeni;
    }
    return yeni_bas;
}
```

#### Takma Ad ile Değişen Düğüm

Liste 1, 2, 3 değerleriyle kuruluyor. `ikinci` işaretçisi ikinci düğümü işaret ediyor —
yeni bir düğüm değil, listenin kendi düğümlerinden biri. `ikinci` üzerinden yapılan
değişiklik listenin toplamına yansıyor, çünkü ikisi de aynı bellek bloğuna bakıyor.

```c
// takma_ad.c — listenin kendi dugumune ikinci bir gostergeyle erisim
#include <stdio.h>
#include "dugum.h"

int main(void) {
    struct Dugum *bas = NULL;
    bas = liste_ekle(bas, 3);
    bas = liste_ekle(bas, 2);
    bas = liste_ekle(bas, 1);

    printf("%s %d\n", "takma adla degismeden once toplam", liste_toplam(bas));

    struct Dugum *ikinci = bas->sonraki;
    ikinci->deger = 20;

    printf("%s %d\n", "takma adla degistikten sonra toplam", liste_toplam(bas));
    printf("%s %d\n", "ikinci ile bas->sonraki ayni dugum mu", (int)(ikinci == bas->sonraki));

    liste_serbest(bas);
    return 0;
}
```

```
takma adla degismeden once toplam 6
takma adla degistikten sonra toplam 24
ikinci ile bas->sonraki ayni dugum mu 1
```

**VY1.** `ikinci` değişkeni `struct Dugum *` tipinde ve `bas` da öyle — kaynağın hiçbir
yerinde `ikinci`'nin listenin **kendi** bir düğümünü mü gösterdiği yoksa bağımsız bir
kopya mı olduğu yazmıyor. Bunu belirleyen, bildirim değil atamanın kendisi: `ikinci =
bas->sonraki` bir kopyalama değil bir **işaretçi kopyalama**, yani `ikinci` ve
`bas->sonraki` aynı adresi taşıyor. `ikinci->deger = 20` satırı bu yüzden `bas` üzerinden
okunan toplamı da değiştiriyor: 6'dan 24'e. Son satır bunu adres basmadan doğruluyor —
`ikinci == bas->sonraki` karşılaştırması, iki işaretçinin aynı düğümü gösterdiğini
**1** olarak veriyor. Sınıf **ortak**: değişikliği ikinci bir okuyucu (burada `bas`
üzerinden yapılan `liste_toplam` çağrısı) görüyor, çünkü ikisi arasında paylaşılan tek bir
nesne var, kopyalanmış iki nesne değil.

#### Sığ Kopya, Derin Kopya

`liste_sig_kopyala` bir düğüm bile ayırmıyor; aldığı başı olduğu gibi döndürüyor. Bu
yüzden döndürdüğü işaretçiyle orijinal liste **aynı düğümlere** bakıyor ve biri üzerinden
yapılan değişiklik ötekinden görünüyor. `liste_derin_kopyala` her düğüm için yeni bir
ayırma yapıyor; döndürdüğü liste kopyalama anındaki değerleri taşıyor ama sonrasında
orijinalle hiçbir düğümü paylaşmıyor.

```c
// kopya_karsilastirma.c — sig kopya paylasiyor, derin kopya kesiyor
#include <stdio.h>
#include "dugum.h"

int main(void) {
    struct Dugum *bas = NULL;
    bas = liste_ekle(bas, 3);
    bas = liste_ekle(bas, 2);
    bas = liste_ekle(bas, 1);

    struct Dugum *sig = liste_sig_kopyala(bas);
    bas->sonraki->deger = 200;
    printf("%s %d\n", "sig kopyanin gordugu ikinci deger", sig->sonraki->deger);

    struct Dugum *derin = liste_derin_kopyala(bas);
    bas->sonraki->deger = 2000;
    printf("%s %d\n", "derin kopyanin gordugu ikinci deger", derin->sonraki->deger);

    liste_serbest(derin);
    liste_serbest(bas);
    return 0;
}
```

```
sig kopyanin gordugu ikinci deger 200
derin kopyanin gordugu ikinci deger 200
```

**VY2.** İki satır da aynı sayıyı (200) basıyor ama farklı nedenlerle ve bu fark dersin
ölçtüğü şey. İlk satırda `sig`, `bas` ile aynı düğümleri gösteriyor; `bas->sonraki->deger =
200` ataması `sig->sonraki->deger`'i de değiştiriyor, çünkü ikisi tek bir nesneye bakıyor
— sınıf **ortak**. İkinci satırda `derin`, kopyalama anında (ikinci düğüm henüz 200
değerini taşırken) yeni düğümler ayırarak kuruldu; kopyalamadan **sonra** yapılan
`bas->sonraki->deger = 2000` ataması `derin`'i etkilemiyor, çünkü `derin`'in düğümleri
`bas`'ınkilerden bağımsız bellek blokları. `derin->sonraki->deger` hâlâ kopyalama anındaki
200'ü taşıyor, 2000'i değil — sınıf **kopya**: değişiklik ikinci bir okuyucuya hiç
ulaşmıyor, çünkü aralarında paylaşılan bir nesne yok. `liste_sig_kopyala`'nın döndürdüğü
işaretçi `bas` ile tıpatıp aynı olduğu için bu derste yalnız `derin` ve `bas` ayrı ayrı
bırakılıyor; `sig`'i ayrıca bırakmak aynı bloğu iki kez bırakmak olurdu.

#### İkili Arama Ağacı: Ekleme, Dolaşma, Bırakma

Bağlı listenin tek-sonraki-işaretçili paylaşımı ikili ağaçta iki işaretçiye (`sol`,
`sag`) çıkıyor ama ilke aynı: bir düğüme birden fazla yoldan erişildiğinde, hangi yoldan
erişildiği kaynakta görünmüyor. `agac_ekle` özyinelemeli, `agac_toplam` sıralı (inorder)
dolaşıp değerleri topluyor, `agac_serbest` önce alt ağaçları sonra kökü bırakıyor —
listedeki "kim bırakacak" sözleşmesi burada da geçerli, bu ders yalnız o sözleşmenin
üzerine değil, ağacın görünürlüğüne odaklanıyor.

```c
// agac.h — ikili arama agaci temel islemleri
#ifndef AGAC_H
#define AGAC_H

struct AgacDugumu {
    int deger;
    struct AgacDugumu *sol;
    struct AgacDugumu *sag;
};

struct AgacDugumu *agac_ekle(struct AgacDugumu *kok, int deger);
int agac_toplam(const struct AgacDugumu *kok);
void agac_serbest(struct AgacDugumu *kok);

#endif
```

```c
// agac.c — ekleme, siralanmis dolasma ve birakma
#include <stdlib.h>
#include "agac.h"

struct AgacDugumu *agac_ekle(struct AgacDugumu *kok, int deger) {
    if (kok == NULL) {
        struct AgacDugumu *yeni = malloc(sizeof *yeni);
        if (yeni == NULL) {
            return NULL;
        }
        yeni->deger = deger;
        yeni->sol = NULL;
        yeni->sag = NULL;
        return yeni;
    }
    if (deger < kok->deger) {
        kok->sol = agac_ekle(kok->sol, deger);
    } else {
        kok->sag = agac_ekle(kok->sag, deger);
    }
    return kok;
}

int agac_toplam(const struct AgacDugumu *kok) {
    if (kok == NULL) {
        return 0;
    }
    return agac_toplam(kok->sol) + kok->deger + agac_toplam(kok->sag);
}

void agac_serbest(struct AgacDugumu *kok) {
    if (kok == NULL) {
        return;
    }
    agac_serbest(kok->sol);
    agac_serbest(kok->sag);
    free(kok);
}
```

```c
// agac_takma_ad.c — agacin kendi dugumune ikinci bir gostergeyle erisim
#include <stdio.h>
#include "agac.h"

int main(void) {
    struct AgacDugumu *kok = NULL;
    int degerler[] = {5, 3, 8, 1, 4};
    for (size_t i = 0; i < sizeof degerler / sizeof degerler[0]; i++) {
        kok = agac_ekle(kok, degerler[i]);
    }
    printf("%s %d\n", "agactaki degerlerin toplami once", agac_toplam(kok));

    struct AgacDugumu *sol_alt = kok->sol;
    sol_alt->deger = 30;
    printf("%s %d\n", "sol_alt uzerinden degisince toplam", agac_toplam(kok));

    agac_serbest(kok);
    return 0;
}
```

```
agactaki degerlerin toplami once 21
sol_alt uzerinden degisince toplam 48
```

**VY3.** `sol_alt`, `kok->sol` ile aynı düğümü gösteriyor — ağaca yeni bir dal eklemedi,
var olan bir düğüme ikinci bir yoldan (kökün `sol` alanı yerine kendi değişkeni üzerinden)
eriştik. `sol_alt->deger = 30` ataması bu düğümün değerini 3'ten 30'a çıkarıyor ve
`agac_toplam(kok)` bunu görüyor: 21'den 48'e. Bağlı listedeki `ikinci` örneğiyle aynı
ilke — sınıf yine **ortak**. Ağacı bırakırken (`agac_serbest`) uyduğumuz sözleşme ayrı bir
soru: her düğümü **kim** bırakacak, önceki kursun (**C'de Bellek ve İşaretçiler**) ekseni.
Bu ders o soruyu yeniden açmıyor; yalnız bir değişikliğin ikinci bir işaretçiden görünüp
görünmediğini ölçüyor.

#### Struct Atamasıyla Kısmi Sığ Kopya

`liste_sig_kopyala` ile `liste_derin_kopyala` iki uç örnekti: biri hiçbir şeyi
kopyalamıyordu, öteki her düğümü kopyalıyordu. Ama C'de bir yapıyı kopyalamanın üçüncü,
daha sinsi bir yolu var: **struct atamasi** (`=`). `struct Dugum kopya = *bas;` yazıldığında
derleyici `bas`'ın gösterdiği yapının **her alanını** yeni bir değişkene kopyalıyor — ama
"her alan"ın içinde bir işaretçi de varsa, o işaretçi de bir değer olarak kopyalanıyor,
gösterdiği yer değil.

```c
// struct_atama.c — degisken tipi atamayla kismi sig kopya
#include <stdio.h>
#include "dugum.h"

int main(void) {
    struct Dugum *bas = NULL;
    bas = liste_ekle(bas, 3);
    bas = liste_ekle(bas, 2);
    bas = liste_ekle(bas, 1);

    struct Dugum kopya = *bas;
    printf("%s %d\n", "atamadan hemen sonra kopyanin deger alani", kopya.deger);

    bas->deger = 100;
    printf("%s %d\n", "bas->deger degisince kopya.deger", kopya.deger);

    bas->sonraki->deger = 200;
    printf("%s %d\n", "ikinci dugum uzerinden degisince kopya.sonraki->deger", kopya.sonraki->deger);

    liste_serbest(bas);
    return 0;
}
```

```
atamadan hemen sonra kopyanin deger alani 1
bas->deger degisince kopya.deger 1
ikinci dugum uzerinden degisince kopya.sonraki->deger 200
```

**VY14.** `struct Dugum kopya = *bas;` satırı iki farklı şey yapıyor ve ikisi de aynı
atamanın içinde: `deger` alanı bir `int`, atama onu **değerle** kopyalıyor — bu yüzden
`bas->deger = 100` çalıştığında `kopya.deger` hâlâ ilk değerinde, 1'de kalıyor. Ama
`sonraki` alanı bir `struct Dugum *`, atama onu da değerle kopyalıyor — kopyalanan değer
bir *adres*, yani `kopya.sonraki` ile `bas->sonraki` atamadan sonra **aynı** adresi
taşıyor. `bas->sonraki->deger = 200` çalıştığında bu değişiklik `kopya.sonraki->deger`
üzerinden de görünüyor: 200. Tek bir atama satırı içinde `deger` alanı için **kopya**
sınıfı, `sonraki` alanının gösterdiği düğüm için **ortak** sınıfı aynı anda üretiliyor —
"sığ kopya" ile "derin kopya" arasındaki fark bazen bir işlev seçimi değil, bir yapının
hangi alanlarının işaretçi taşıdığı meselesi. `liste_sig_kopyala`'nın döndürdüğü işaretçinin
tamamı paylaşılıyordu; burada yalnız `sonraki`'nin gösterdiği düğüm paylaşılıyor, `deger`
alanının kendisi paylaşılmıyor — aynı struct atamasının içinde iki ayrı sınıf bir arada.

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

Bir işaretçinin paylaşımı kaynakta görünmez. İki liste başı ya da iki ağaç dalı aynı
düğümü gösterdiğinde bunu yapının tipi söylemez — `struct Dugum *` bildirimi hem
listenin kendi bir düğümünü hem bağımsız bir kopyayı gösterebilir, aralarında kaynak
düzeyinde hiçbir ayrım yok. Bu derste her iki durumda da işaretçileri adres basmadan
karşılaştırdık: `ikinci == bas->sonraki` eşitliği, iki değişkenin aynı bellek bloğuna mı
baktığını, farklı bloklara mı baktığını söyledi. `liste_sig_kopyala` ile `liste_derin_
kopyala`'nın farkı da kaynakta bir imzayla değil, gövdelerinin ne yaptığıyla ortaya
çıkıyor: biri hiç ayırma yapmıyor, öteki her düğüm için yeni bir ayırma yapıyor. Bir
işlevin dönüş tipine bakarak paylaşıp paylaşmadığını söylemek mümkün değil; bu yalnız
gövdeyi okuyarak ya da ölçerek anlaşılıyor.

#### Özet

- Bağlı liste ve ikili arama ağacı aynı ilkeyle kuruldu: bir düğüme birden fazla
  işaretçi aynı anda erişebilir ve kaynak bunu söylemez.
- Bir düğüme takma adla erişip değiştirmek listede de ağaçta da **ortak** sınıfını
  üretti: ikinci bir okuyucu (`liste_toplam`, `agac_toplam`) değişikliği gördü.
- `liste_sig_kopyala` düğüm ayırmadan başı döndürdüğü için **ortak** kaldı;
  `liste_derin_kopyala` her düğüm için yeni bir ayırma yaptığı için **kopya** üretti.
- Struct atamasıyla (`=`) yapılan kısmi kopya, bir yapının skaler alanlarında **kopya**,
  işaretçi alanlarının işaret ettiği yerde **ortak** sınıfını aynı anda üretebiliyor.
- İki işaretçinin aynı düğümü gösterip göstermediği adres basılmadan, yalnız eşitlikle
  (`==`) ölçülebiliyor.
- Bir yapının **kim tarafından bırakılacağı** (K02'nin sorusu) ile bir değişikliğin
  **kim tarafından görüleceği** (bu kursun sorusu) ayrı eksenler; bu ders ikincisini
  ölçüyor.

#### Sonraki Adım

Bu derste bir düğüme erişmek için hep bir işaretçi tuttuk ve o işaretçi kalıcıydı. Bir
sonraki ders, Halka Arabellek ve Kuyruklar, işaretçinin kendisi değil bir **konumun**
(indeksin) paylaşıldığı bir yapıya bakıyor: bir üretici ile bir tüketici aynı diziyi
kullanıyor ama biri yazarken diğeri okuyor, ve yazılan değer okunmadan üzerine
yazılabiliyor. Sınıf orada **ortak** değil, **aktarım**: değer okuyucuya bir kez ulaşıyor,
sonra kaybolabiliyor.

### Halka Arabellek ve Kuyruklar

Source: https://academia.sh/tr/kurslar/c-sistem-programlama/halka-arabellek-ve-kuyruklar

Sabit kapasiteli halka arabellek ve bağlı kuyrukta bir değerin üretici ile tüketici arasında tam bir kez geçmesi; okunan değerin arabellekte kalmadığının eşitlikle ölçülmesi.

Önceki ders bir düğümün iki işaretçiden aynı anda görüldüğü **ortak** sınıfını ölçtü:
değişiklik ne zaman okunursa okunsun oradaydı, çünkü paylaşılan nesne kalıcıydı. Bu ders
farklı bir yapıyla farklı bir sınıfı ölçüyor: bir üretici bir değeri bir arabelleğe
yazıyor, bir tüketici onu okuyor — ama okuma bir **alma** işlemi, bir **bakma** değil.
Değer bir kez okunduktan sonra arabellekte kalmıyor; ikinci bir okuma boş döner. Bu
davranış dört sınıftan **aktarım**'ı tanımlıyor: değer bir aracıdan geçiyor ve bir
okuyucuya ulaşıyor, ama o okuyucudan sonra kalıcı bir görünürlüğü yok.

#### Halka Arabellek: Sabit Kapasiteli Yazma ve Okuma

Arabellek dört elemanlık sabit bir dizi tutuyor; `bas` bir sonraki okumanın, `son` bir
sonraki yazmanın index'ini, `sayi` dolu eleman sayısını taşıyor. `halka_yaz` arabellek
doluysa yazmıyor ve `0` döndürüyor; `halka_oku` arabellek boşsa okumuyor ve `0`
döndürüyor. Her ikisi de başarılıysa `1` döndürüyor.

```c
// halka.h — sabit kapasiteli halka arabellek
#ifndef HALKA_H
#define HALKA_H

#define HALKA_KAPASITE 4

struct Halka {
    int veri[HALKA_KAPASITE];
    int bas;
    int son;
    int sayi;
};

void halka_hazirla(struct Halka *h);
int halka_yaz(struct Halka *h, int deger);
int halka_oku(struct Halka *h, int *hedef);

#endif
```

```c
// halka.c — halka arabellegin gerceklemesi
#include "halka.h"

void halka_hazirla(struct Halka *h) {
    h->bas = 0;
    h->son = 0;
    h->sayi = 0;
}

int halka_yaz(struct Halka *h, int deger) {
    if (h->sayi == HALKA_KAPASITE) {
        return 0;
    }
    h->veri[h->son] = deger;
    h->son = (h->son + 1) % HALKA_KAPASITE;
    h->sayi = h->sayi + 1;
    return 1;
}

int halka_oku(struct Halka *h, int *hedef) {
    if (h->sayi == 0) {
        return 0;
    }
    *hedef = h->veri[h->bas];
    h->bas = (h->bas + 1) % HALKA_KAPASITE;
    h->sayi = h->sayi - 1;
    return 1;
}
```

#### Bir Değerin Arabellekten Tam Bir Kez Geçmesi

Üç değer sırayla yazılıyor, sonra sırayla okunuyor. Sıra korunuyor (ilk yazılan ilk
okunuyor), ama asıl ölçüm son iki satırda: aynı değeri ikinci kez okumaya çalışmak boş
dönüyor, çünkü değer birinci okumada arabellekten **alındı**, orada bırakılmadı.

```c
// bir_kez_gecis.c — uc deger yazilip okunuyor, sonra arabellek bos kaliyor
#include <stdio.h>
#include "halka.h"

int main(void) {
    struct Halka h;
    halka_hazirla(&h);

    halka_yaz(&h, 10);
    halka_yaz(&h, 20);
    halka_yaz(&h, 30);

    int deger = 0;
    halka_oku(&h, &deger);
    printf("%s %d\n", "ilk okunan deger", deger);
    halka_oku(&h, &deger);
    printf("%s %d\n", "ikinci okunan deger", deger);
    halka_oku(&h, &deger);
    printf("%s %d\n", "ucuncu okunan deger", deger);

    int basari = halka_oku(&h, &deger);
    printf("%s %d\n", "dorduncu okuma basarili mi", basari);
    printf("%s %d\n", "dorduncu okumadan sonra deger degisti mi", (int)(deger == 30));

    return 0;
}
```

```
ilk okunan deger 10
ikinci okunan deger 20
ucuncu okunan deger 30
dorduncu okuma basarili mi 0
dorduncu okumadan sonra deger degisti mi 1
```

**VY4.** `halka_oku`'nun imzası (`int halka_oku(struct Halka *h, int *hedef)`) bir
işaretçinin **kim tarafından bırakılacağını** söylemediği gibi, aldığı değerin arabellekte
kalıp kalmayacağını da söylemiyor — bunu belirleyen `h->sayi = h->sayi - 1` satırı,
gövdenin kendisi. Üç değer sırayla (10, 20, 30) çıkıyor, çünkü `bas` her okumada bir
ilerliyor ve arabellek ilk giren ilk çıkan sırasını koruyor. Dördüncü çağrı `0` döndürüyor
çünkü `sayi` sıfıra düştü; `hedef`'e hiçbir şey yazılmadığı için `deger` bir önceki
çağrıdan kalan **30**'u taşımaya devam ediyor — bu, `deger`'in kendisinin değişmediğinin
kanıtı, arabellekte yeni bir değer olduğunun değil. Sınıf **aktarım**: her değer tam bir
okuyucuya ulaştı ve o okuyucudan sonra arabellekte bir izi kalmadı; **ortak** sınıfından
farkı burada — orada paylaşılan nesne okunduktan sonra da duruyordu, burada okunan yer
boşalıyor.

#### Üretici ile Tüketicinin Sırayla Çalışması

Yazma ve okuma birbirine karışabiliyor: iki yazma, bir okuma, bir yazma daha, iki okuma.
Bu, üretici ile tüketicinin aynı arabelleği farklı hızlarda kullandığı gerçek durumu
taklit ediyor. Arabellek en fazla dört eleman tutabildiği için üçüncü yazmadan önceki
okuma yer açıyor.

```c
// karisik_sira.c — yazma ve okuma cagrilari ic ice
#include <stdio.h>
#include "halka.h"

int main(void) {
    struct Halka h;
    halka_hazirla(&h);
    int deger = 0;

    halka_yaz(&h, 1);
    halka_yaz(&h, 2);
    halka_oku(&h, &deger);
    printf("%s %d\n", "iki yazmadan sonraki okuma", deger);

    halka_yaz(&h, 3);
    halka_oku(&h, &deger);
    printf("%s %d\n", "uc yazmadan sonraki ikinci okuma", deger);
    halka_oku(&h, &deger);
    printf("%s %d\n", "uc yazmadan sonraki ucuncu okuma", deger);

    int basari = halka_oku(&h, &deger);
    printf("%s %d\n", "dorduncu okuma basarili mi", basari);

    return 0;
}
```

```
iki yazmadan sonraki okuma 1
uc yazmadan sonraki ikinci okuma 2
uc yazmadan sonraki ucuncu okuma 3
dorduncu okuma basarili mi 0
```

**VY5.** Yazma ve okuma çağrıları ne sırayla iç içe geçerse geçsin, her okuma en eski
**henüz okunmamış** değeri veriyor — bu, `bas`'ın yalnız okumada ilerlemesinin,
`son`'un yalnız yazmada ilerlemesinin sonucu. İkinci koşum birinciyle aynı üç değeri
(bu kez 1, 2, 3) aynı sırayla üretiyor, ama aralarına bir yazma daha girmiş olması
sonucu değiştirmiyor: arabellek her an en fazla dört eleman tutabiliyor ve üçüncü
yazmadan önceki okuma yer açtığı için taşma hiç gerçekleşmiyor. Bir değerin ne zaman
yazıldığı ile ne zaman okunduğu arasındaki bu esneklik, aktarımın ikinci bir özelliği:
üretici ile tüketici aynı hızda çalışmak zorunda değil, aralarındaki arabellek bir tampon
görevi görüyor — ama kapasitenin üzerinde yazmaya çalışmanın ne olacağı (okunmamış bir
değerin üzerine yazılması) bu dersin konusu değil; o soru **gözlenmeyen** sınıfını ölçen
bir sonraki dersin.

#### Kuyruk: Sınırsız Kapasitede Aynı İlke

Halka arabellek sabit kapasiteli; kuyruk aynı ilk-giren-ilk-çıkan sırasını bağlı
düğümlerle, kapasite sınırı olmadan uyguluyor. `kuyruk_ekle` sona bir düğüm ekliyor,
`kuyruk_cikar` baştaki düğümü çıkarıp değerini döndürüyor ve düğümü bırakıyor — okunan
değer burada da kuyrukta kalmıyor.

```c
// kuyruk.h — bagli dugumlerle sinirsiz kapasiteli kuyruk
#ifndef KUYRUK_H
#define KUYRUK_H

struct KuyrukDugumu {
    int deger;
    struct KuyrukDugumu *sonraki;
};

struct Kuyruk {
    struct KuyrukDugumu *bas;
    struct KuyrukDugumu *son;
};

void kuyruk_hazirla(struct Kuyruk *k);
void kuyruk_ekle(struct Kuyruk *k, int deger);
int kuyruk_cikar(struct Kuyruk *k, int *hedef);

#endif
```

```c
// kuyruk.c — kuyruk islemlerinin gerceklemesi
#include <stdlib.h>
#include "kuyruk.h"

void kuyruk_hazirla(struct Kuyruk *k) {
    k->bas = NULL;
    k->son = NULL;
}

void kuyruk_ekle(struct Kuyruk *k, int deger) {
    struct KuyrukDugumu *yeni = malloc(sizeof *yeni);
    if (yeni == NULL) {
        return;
    }
    yeni->deger = deger;
    yeni->sonraki = NULL;
    if (k->son == NULL) {
        k->bas = yeni;
    } else {
        k->son->sonraki = yeni;
    }
    k->son = yeni;
}

int kuyruk_cikar(struct Kuyruk *k, int *hedef) {
    if (k->bas == NULL) {
        return 0;
    }
    struct KuyrukDugumu *eski = k->bas;
    *hedef = eski->deger;
    k->bas = eski->sonraki;
    if (k->bas == NULL) {
        k->son = NULL;
    }
    free(eski);
    return 1;
}
```

```c
// kuyruk_gecisi.c — kuyruktan cikan deger de bir kez ulasiyor
#include <stdio.h>
#include "kuyruk.h"

int main(void) {
    struct Kuyruk k;
    kuyruk_hazirla(&k);

    kuyruk_ekle(&k, 100);
    kuyruk_ekle(&k, 200);

    int deger = 0;
    kuyruk_cikar(&k, &deger);
    printf("%s %d\n", "kuyruktan ilk cikan deger", deger);
    kuyruk_cikar(&k, &deger);
    printf("%s %d\n", "kuyruktan ikinci cikan deger", deger);

    int basari = kuyruk_cikar(&k, &deger);
    printf("%s %d\n", "bos kuyruktan cikarma basarili mi", basari);

    return 0;
}
```

```
kuyruktan ilk cikan deger 100
kuyruktan ikinci cikan deger 200
bos kuyruktan cikarma basarili mi 0
```

**VY6.** Kuyruk, halka arabellekten farklı bir bellek modeli kullanıyor — sabit bir dizi
yerine her eleman kendi ayrılmış düğümünde duruyor ve `kuyruk_cikar` düğümü okuduktan
sonra bırakıyor (`free(eski)`). Ama ölçülen sınıf aynı: her değer tam bir çağrıya ulaşıyor
ve o çağrıdan sonra kuyrukta bir izi kalmıyor. Kapasite sınırının olmaması (`kuyruk_ekle`
her zaman başarılı, `malloc` başarısız olmadıkça) aktarım sınıfını değiştirmiyor; yalnız
halka arabellekteki "dolu" durumunu ortadan kaldırıyor.

#### Dolulukta Reddet ya da Ez Politikası

`halka_yaz` dolu arabellekte yazmayı reddediyor. Ama bazı üretici/tüketici düzenlerinde
en yeni değer en eski değerden daha önemlidir — bu durumda dolulukta reddetmek yerine
en eski elemanın üzerine yazmak istenir. İki politika da aynı `struct Halka` üzerinde
çalışabiliyor; farkları yalnız dolulukta ne yaptıklarında.

```c
// halka_ez.h — dolulukta en eskiyi ezen yazma politikasinin bildirimi
#ifndef HALKA_EZ_H
#define HALKA_EZ_H

#include "halka.h"

int halka_yaz_ez(struct Halka *h, int deger);

#endif
```

```c
// halka_ez.c — dolulukta en eskiyi ezen yazma politikasinin gerceklemesi
#include "halka_ez.h"

int halka_yaz_ez(struct Halka *h, int deger) {
    if (h->sayi == HALKA_KAPASITE) {
        h->bas = (h->bas + 1) % HALKA_KAPASITE;
    } else {
        h->sayi = h->sayi + 1;
    }
    h->veri[h->son] = deger;
    h->son = (h->son + 1) % HALKA_KAPASITE;
    return 1;
}
```

```c
// doluluk_politikasi.c — reddet ve ez politikalarinin karsilastirilmasi
#include <stdio.h>
#include "halka_ez.h"

int main(void) {
    struct Halka h;
    halka_hazirla(&h);

    halka_yaz(&h, 10);
    halka_yaz(&h, 20);
    halka_yaz(&h, 30);
    halka_yaz(&h, 40);

    int reddedildi_mi = halka_yaz(&h, 50);
    printf("%s %d\n", "dolu arabellege reddet politikasiyla yazma sonucu", reddedildi_mi);

    int ez_sonucu = halka_yaz_ez(&h, 99);
    printf("%s %d\n", "dolu arabellege ez politikasiyla yazma sonucu", ez_sonucu);

    int hedef;
    for (int i = 0; i < HALKA_KAPASITE; i++) {
        halka_oku(&h, &hedef);
        printf("%s %d\n", "ez sonrasi sirayla okunan deger", hedef);
    }

    return 0;
}
```

```
dolu arabellege reddet politikasiyla yazma sonucu 0
dolu arabellege ez politikasiyla yazma sonucu 1
ez sonrasi sirayla okunan deger 20
ez sonrasi sirayla okunan deger 30
ez sonrasi sirayla okunan deger 40
ez sonrasi sirayla okunan deger 99
```

**VY15.** İki politika da dolu arabellekte farklı bir seçim yapıyor ama ikisi de
deterministik: `halka_yaz` dolulukta hiçbir şey değiştirmeden `0` dönüyor — `50` değeri
yapıya hiç girmiyor. `halka_yaz_ez` ise `bas`'ı bir ileri kaydırıp en eski elemanın
(`10`) durduğu index'in üzerine yazıyor; `10` hiçbir okumaya ulaşmadan kayboluyor. Bu,
dört sınıftan **gözlenmeyen**'i tanımlıyor: değer yapıya girdi ama hiçbir okuyucuya
ulaşmadan üzerine yazıldı. Reddedilen `50` ile ezilen `10` arasındaki fark önemli — `50`
hiç yazılmadı, `10` yazıldı ve sonra kayboldu; ikisi de sonunda okunamıyor ama farklı
nedenlerle.

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

Bir okuma işlevinin imzası, okuduğu değerin kaynakta kalıp kalmayacağını söylemiyor.
`int halka_oku(struct Halka *h, int *hedef)` ile `int liste_toplam(const struct Dugum
*bas)` (önceki ders) aynı ailede: ikisi de bir yapıyı okuyor. Ama biri okuduğu değeri
yapının içinde bırakıyor (liste kalıcı), öteki okuduğu değeri yapıdan çıkarıyor (arabellek
boşalıyor). Bu fark parametre tiplerinde, dönüş tipinde, hatta işlev adında görünmüyor;
yalnız gövdenin `sayi`'yi azaltıp azaltmadığına bakılarak ya da art arda iki çağrı
yapılıp sonuçları karşılaştırılarak ölçülebiliyor — tıpkı bu derste yapıldığı gibi.

#### Özet

- Halka arabellek sabit kapasiteli bir dizide, kuyruk bağlı düğümlerde sınırsız
  kapasitede aynı ilk-giren-ilk-çıkan sırasını uyguluyor.
- Bir değer okunduktan sonra arabellekte ya da kuyrukta kalmıyor; ikinci bir okuma ya
  da çıkarma boş dönüyor — sınıf **aktarım**.
- Aktarımın **ortak**'tan farkı: ortakta paylaşılan nesne okunduktan sonra da duruyor,
  aktarımda okunan yer boşalıyor ve değer bir daha okunamıyor.
- Üretici ile tüketicinin yazma/okuma sırası iç içe geçebilir; arabellek aralarında bir
  tampon görevi görüyor.
- Dolulukta reddetme ile en eskiyi ezme iki ayrı politika; reddedilen değer yapıya hiç
  girmiyor, ezilen değer ise girip hiçbir okumaya ulaşmadan kayboluyor — bu ikincisi
  **gözlenmeyen** sınıfı tanımlıyor.
- Bir okuma işlevinin okuduğu değeri kaynakta bırakıp bırakmadığı imzada görünmüyor;
  yalnız gövde okunarak ya da art arda iki çağrı karşılaştırılarak ölçülebiliyor.

#### Sonraki Adım

Bu derste bir değerin bir kez okunup arabellekten çıktığını ölçtük. Bir sonraki ders,
Karma Tabloları, farklı bir soruyla ilerliyor: bir anahtarla saklanan bir değer, aynı
anahtarla tekrar tekrar okunduğunda hep aynı sonucu mu veriyor, yoksa okuma sırasında bir
kopyası mı üretiliyor? Orada ölçülen sınıf **kopya**: tablodan okunan değer, tablonun
kendi kopyası değil, çağıranın aldığı bağımsız bir kopya.

### Karma Tabloları

Source: https://academia.sh/tr/kurslar/c-sistem-programlama/karma-tablolari

Ayrık zincirlemeli bir karma tablosunda bir kaydın okunmasının kendi kopyasını ürettiği, tablodaki kaydı değiştirmediği; çakışan anahtarların aynı kovada zincirlendiği ölçümü.

Önceki iki ders bir değerin ya paylaşılan bir nesne üzerinden kalıcı biçimde göründüğünü
(**ortak**) ya da bir aracıdan tam bir kez geçip kaybolduğunu (**aktarım**) ölçtü. Bu ders
üçüncü bir sınıfı, **kopya**'yı ölçüyor: bir karma tablosundan bir kaydı okumak, o kaydı
tablodan çıkarmıyor ve tablodaki kayıtla paylaşılan bir nesne de üretmiyor — çağırana
kaydın **kendi kopyasını** veriyor. Bu kopya üzerinde yapılan bir değişiklik tabloya hiç
yansımıyor, çünkü ikisi arasında ortak hiçbir bellek bloğu yok.

#### Karma Tablo: Ayrık Zincirleme ile Ekleme ve Bulma

Tablo sabit sayıda kovadan oluşuyor; her kova bir bağlı liste (zincir). `karma_indeks`
bir anahtarı kova sayısına göre bir indekse eşliyor. `karma_ekle` anahtar zincirde varsa
kaydı güncelliyor, yoksa zincirin başına yeni bir düğüm ekliyor. `karma_bul` kaydı
bulursa **kopyasını** `hedef`'e yazıyor ve `1` döndürüyor; bulamazsa `0` döndürüyor.

```c
// karma.h — ayrik zincirlemeli karma tablo
#ifndef KARMA_H
#define KARMA_H

#define KARMA_KAPASITE 8

struct Kayit {
    int miktar;
    int sayac;
};

struct KarmaDugumu {
    int anahtar;
    struct Kayit deger;
    struct KarmaDugumu *sonraki;
};

struct KarmaTablo {
    struct KarmaDugumu *kovalar[KARMA_KAPASITE];
};

void karma_hazirla(struct KarmaTablo *t);
void karma_ekle(struct KarmaTablo *t, int anahtar, struct Kayit deger);
int karma_bul(const struct KarmaTablo *t, int anahtar, struct Kayit *hedef);
void karma_serbest(struct KarmaTablo *t);

#endif
```

```c
// karma.c — karma tablo islemlerinin gerceklemesi
#include <stdlib.h>
#include "karma.h"

static int karma_indeks(int anahtar) {
    return (anahtar % KARMA_KAPASITE + KARMA_KAPASITE) % KARMA_KAPASITE;
}

void karma_hazirla(struct KarmaTablo *t) {
    for (int i = 0; i < KARMA_KAPASITE; i++) {
        t->kovalar[i] = NULL;
    }
}

void karma_ekle(struct KarmaTablo *t, int anahtar, struct Kayit deger) {
    int i = karma_indeks(anahtar);
    for (struct KarmaDugumu *d = t->kovalar[i]; d != NULL; d = d->sonraki) {
        if (d->anahtar == anahtar) {
            d->deger = deger;
            return;
        }
    }
    struct KarmaDugumu *yeni = malloc(sizeof *yeni);
    if (yeni == NULL) {
        return;
    }
    yeni->anahtar = anahtar;
    yeni->deger = deger;
    yeni->sonraki = t->kovalar[i];
    t->kovalar[i] = yeni;
}

int karma_bul(const struct KarmaTablo *t, int anahtar, struct Kayit *hedef) {
    int i = karma_indeks(anahtar);
    for (struct KarmaDugumu *d = t->kovalar[i]; d != NULL; d = d->sonraki) {
        if (d->anahtar == anahtar) {
            *hedef = d->deger;
            return 1;
        }
    }
    return 0;
}

void karma_serbest(struct KarmaTablo *t) {
    for (int i = 0; i < KARMA_KAPASITE; i++) {
        struct KarmaDugumu *d = t->kovalar[i];
        while (d != NULL) {
            struct KarmaDugumu *sonraki = d->sonraki;
            free(d);
            d = sonraki;
        }
    }
}
```

#### Okunan Kaydın Kendi Kopyası Olması

Bir kayıt eklenip okunuyor, sonra okunan kopya değiştiriliyor. Asıl ölçüm, bu
değişiklikten sonra tablodan **tekrar** okunan değerin ilk hâlini korumasında.

```c
// bulunan_kopya.c — okunan kayit degistiriliyor, tablo etkilenmiyor mu
#include <stdio.h>
#include "karma.h"

int main(void) {
    struct KarmaTablo t;
    karma_hazirla(&t);

    struct Kayit ilk = {100, 1};
    karma_ekle(&t, 3, ilk);

    struct Kayit okunan;
    karma_bul(&t, 3, &okunan);
    printf("%s %d\n", "ilk okunan miktar", okunan.miktar);

    okunan.miktar = 900;
    printf("%s %d\n", "kopya degistirildikten sonra kopyanin miktari", okunan.miktar);

    struct Kayit tekrar_okunan;
    karma_bul(&t, 3, &tekrar_okunan);
    printf("%s %d\n", "tablodan tekrar okunan miktar", tekrar_okunan.miktar);

    karma_serbest(&t);
    return 0;
}
```

```
ilk okunan miktar 100
kopya degistirildikten sonra kopyanin miktari 900
tablodan tekrar okunan miktar 100
```

**VY7.** `karma_bul`'un imzası (`int karma_bul(const struct KarmaTablo *t, int anahtar,
struct Kayit *hedef)`) `hedef`'e yazılan `struct Kayit`'in tablonun kendi kaydı mı yoksa
bağımsız bir kopyası mı olduğunu söylemiyor — bunu belirleyen `*hedef = d->deger` satırı,
yani bir **yapı ataması**. C'de bir yapıyı bir başka yapıya atamak, alanlarını tek tek
kopyalıyor; `okunan` bu atamadan sonra `d->deger` ile aynı bellek bloğunu göstermiyor,
kendi bağımsız bloğunda duruyor. `okunan.miktar = 900` ataması bu yüzden yalnız `okunan`'ı
değiştiriyor; tablo parametresi `karma_bul`'a zaten `const struct KarmaTablo *` olarak
geçiyor ve işlev onu hiç değiştirmiyor. İkinci `karma_bul` çağrısı tablodan **yeniden**
okuyor ve ilk değeri (**100**) veriyor — 900'ü değil. Sınıf **kopya**: okunan kayıt
üzerinde yapılan bir değişiklik ikinci bir okuyucuya (burada tablonun kendisi) hiç
ulaşmıyor, çünkü aralarında paylaşılan bir nesne yok.

#### Aynı Anahtarla Yeniden Ekleme

`karma_ekle` aynı anahtarla ikinci kez çağrıldığında yeni bir düğüm eklemiyor, var olan
düğümün `deger` alanını güncelliyor. Bu, önceden okunmuş bir kopyayı etkilemiyor — kopya
zaten ayrı bir bellek bloğunda.

```c
// yeniden_ekleme.c — ayni anahtarla guncelleme, onceki kopyayi etkilemiyor
#include <stdio.h>
#include "karma.h"

int main(void) {
    struct KarmaTablo t;
    karma_hazirla(&t);

    struct Kayit ilk = {5, 1};
    karma_ekle(&t, 7, ilk);

    struct Kayit once_okunan;
    karma_bul(&t, 7, &once_okunan);
    printf("%s %d\n", "guncellemeden once okunan miktar", once_okunan.miktar);

    struct Kayit guncel = {50, 2};
    karma_ekle(&t, 7, guncel);

    printf("%s %d\n", "guncellemeden sonra eski kopyanin miktari", once_okunan.miktar);

    struct Kayit sonra_okunan;
    karma_bul(&t, 7, &sonra_okunan);
    printf("%s %d\n", "guncellemeden sonra tablodan okunan miktar", sonra_okunan.miktar);

    karma_serbest(&t);
    return 0;
}
```

```
guncellemeden once okunan miktar 5
guncellemeden sonra eski kopyanin miktari 5
guncellemeden sonra tablodan okunan miktar 50
```

**VY8.** `once_okunan`, `karma_ekle(&t, 7, guncel)` çağrısından önce alınmış bir kopya —
tabloyla hiçbir bağı kalmadığı için tablo güncellendiğinde değişmiyor, **5**'te kalıyor.
Tablodan yeniden okunan `sonra_okunan` ise güncel değeri (**50**) görüyor, çünkü bu
kez `karma_bul` güncellemeden **sonra** çağrıldı ve tablonun o andaki hâlinden yeni bir
kopya aldı. Bu iki satır birlikte kopya sınıfının sınırını gösteriyor: bir kopya, alındığı
andaki değeri sonsuza dek taşıyor; kaynağı sonradan değişse bile kopya bunu **hiç**
görmüyor, çünkü görmesi için kaynakla aralarında paylaşılan bir bağ gerekirdi.

#### Çakışan Anahtarların Aynı Kovada Zincirlenmesi

Kapasite sekiz olduğu için 3 ve 11 anahtarları aynı kovaya (`3 % 8 = 3`, `11 % 8 = 3`)
düşüyor. `karma_ekle` bu durumda ikinci düğümü zincirin başına ekliyor; `karma_bul` doğru
anahtarı zincirde arayarak buluyor.

```c
// cakisan_anahtarlar.c — ayni kovaya dusen iki farkli anahtar
#include <stdio.h>
#include "karma.h"

int main(void) {
    struct KarmaTablo t;
    karma_hazirla(&t);

    struct Kayit birinci = {11, 0};
    struct Kayit ikinci = {22, 0};
    karma_ekle(&t, 3, birinci);
    karma_ekle(&t, 11, ikinci);

    struct Kayit sonuc;
    karma_bul(&t, 3, &sonuc);
    printf("%s %d\n", "anahtar 3 icin bulunan miktar", sonuc.miktar);
    karma_bul(&t, 11, &sonuc);
    printf("%s %d\n", "anahtar 11 icin bulunan miktar", sonuc.miktar);

    karma_serbest(&t);
    return 0;
}
```

```
anahtar 3 icin bulunan miktar 11
anahtar 11 icin bulunan miktar 22
```

**VY9.** İki anahtar aynı kovaya düştüğü hâlde `karma_bul` doğru kaydı buluyor, çünkü
arama yalnız kovanın indeksine değil, zincirdeki her düğümün `anahtar` alanına da
bakıyor (`d->anahtar == anahtar`). Bu, karma tablonun neden bir dizi değil bir **dizi
listeler** olduğunu gösteriyor: bir kovaya birden fazla anahtar düşebiliyor ve zincir bu
çakışmayı çözüyor. Çakışmanın kendisi bu dersin ölçtüğü sınıfı değiştirmiyor — her iki
`karma_bul` çağrısı da yine kendi kopyasını üretiyor.

#### Anahtarı İşaretçiyle Tutan Sürüm

Şimdiye kadarki tablo anahtarı bir `int` olarak **değerle** tutuyordu — `karma_ekle`'ye
geçen anahtar kopyalanıyor, düğüm kendi bağımsız kopyasını saklıyor. Anahtarı bunun
yerine bir işaretçiyle tutan bir sürüm de yazılabilir; bu, anahtarın ölçtüğü sınıfı
**kopya**'dan **ortak**'a çeviriyor ve farklı bir tehlike doğuruyor.

```c
// karma_ptr.h — anahtari isaretciyle tutan karma tablo surumunun bildirimi
#ifndef KARMA_PTR_H
#define KARMA_PTR_H

#include "karma.h"

struct KarmaDugumuPtr {
    const int *anahtar;
    struct Kayit deger;
    struct KarmaDugumuPtr *sonraki;
};

struct KarmaTabloPtr {
    struct KarmaDugumuPtr *kovalar[KARMA_KAPASITE];
};

void karma_ptr_hazirla(struct KarmaTabloPtr *t);
void karma_ptr_ekle(struct KarmaTabloPtr *t, const int *anahtar, struct Kayit deger);
int karma_ptr_bul(const struct KarmaTabloPtr *t, int anahtar_degeri, struct Kayit *hedef);
void karma_ptr_serbest(struct KarmaTabloPtr *t);

#endif
```

```c
// karma_ptr.c — anahtari isaretciyle tutan karma tablo surumunun gerceklemesi
#include <stdlib.h>
#include "karma_ptr.h"

static int karma_ptr_indeks(int anahtar_degeri) {
    return (anahtar_degeri % KARMA_KAPASITE + KARMA_KAPASITE) % KARMA_KAPASITE;
}

void karma_ptr_hazirla(struct KarmaTabloPtr *t) {
    for (int i = 0; i < KARMA_KAPASITE; i++) {
        t->kovalar[i] = NULL;
    }
}

void karma_ptr_ekle(struct KarmaTabloPtr *t, const int *anahtar, struct Kayit deger) {
    int i = karma_ptr_indeks(*anahtar);
    struct KarmaDugumuPtr *yeni = malloc(sizeof *yeni);
    if (yeni == NULL) {
        return;
    }
    yeni->anahtar = anahtar;
    yeni->deger = deger;
    yeni->sonraki = t->kovalar[i];
    t->kovalar[i] = yeni;
}

int karma_ptr_bul(const struct KarmaTabloPtr *t, int anahtar_degeri, struct Kayit *hedef) {
    int i = karma_ptr_indeks(anahtar_degeri);
    for (struct KarmaDugumuPtr *d = t->kovalar[i]; d != NULL; d = d->sonraki) {
        if (*(d->anahtar) == anahtar_degeri) {
            *hedef = d->deger;
            return 1;
        }
    }
    return 0;
}

void karma_ptr_serbest(struct KarmaTabloPtr *t) {
    for (int i = 0; i < KARMA_KAPASITE; i++) {
        struct KarmaDugumuPtr *d = t->kovalar[i];
        while (d != NULL) {
            struct KarmaDugumuPtr *sonraki = d->sonraki;
            free(d);
            d = sonraki;
        }
    }
}
```

```c
// anahtar_isaretci.c — anahtari tutan degisken sonradan degisince arama bozuluyor mu
#include <stdio.h>
#include "karma_ptr.h"

int main(void) {
    struct KarmaTabloPtr t;
    karma_ptr_hazirla(&t);

    int anahtar_degiskeni = 5;
    struct Kayit deger = {42, 1};
    karma_ptr_ekle(&t, &anahtar_degiskeni, deger);

    struct Kayit bulunan;
    int bulundu_mu = karma_ptr_bul(&t, 5, &bulunan);
    printf("%s %d\n", "degisiklikten once anahtar 5 ile bulundu mu", bulundu_mu);

    anahtar_degiskeni = 99;

    int eski_deger_ile_bulundu_mu = karma_ptr_bul(&t, 5, &bulunan);
    printf("%s %d\n", "degiskeni 99 yaptiktan sonra eski deger 5 ile bulundu mu", eski_deger_ile_bulundu_mu);

    int yeni_deger_ile_bulundu_mu = karma_ptr_bul(&t, 99, &bulunan);
    printf("%s %d\n", "degiskeni 99 yaptiktan sonra yeni deger 99 ile bulundu mu", yeni_deger_ile_bulundu_mu);

    karma_ptr_serbest(&t);
    return 0;
}
```

```
degisiklikten once anahtar 5 ile bulundu mu 1
degiskeni 99 yaptiktan sonra eski deger 5 ile bulundu mu 0
degiskeni 99 yaptiktan sonra yeni deger 99 ile bulundu mu 0
```

**VY16.** `karma_ptr_ekle` düğümün kovasını `*anahtar` — yani `anahtar_degiskeni`'nin
**o andaki** değeri — üzerinden hesaplıyor ve bu kova bir daha değişmiyor. Ama düğüm
anahtarı kopyalamıyor, `anahtar_degiskeni`'nin adresini tutuyor; bu yüzden `anahtar`
alanı ile çağıranın değişkeni arasında bir **ortak** ilişkisi var. `anahtar_degiskeni`
`99`'a değiştiğinde `d->anahtar`'ın gösterdiği bellek de `99` oluyor, ama düğüm hâlâ
`5`'in kovasında duruyor. Sonuç: `5` ile arandığında kovadaki düğümün anahtarı artık
`5` değil (bulunamıyor); `99` ile arandığında doğru kova (`99`'un kovası) boş (yine
bulunamıyor). Kayıt hiçbir anahtarla erişilemez hâle geliyor — kovaya yerleştirme bir
kerelik bir karardı, ama karşılaştırma canlı belleği okumaya devam ediyor. `karma_ekle`
bu tehlikeden bağışık, çünkü anahtarı değerle kopyalıyor; anahtarı işaretçiyle tutmak bu
korumayı kaybediyor.

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

Bir bulma işlevinin dönüş tipi, döndürdüğü kaydın tablonun kendi belleğiyle paylaşılıp
paylaşılmadığını söylemiyor. `struct Kayit *hedef` parametresi bir işaretçi taşıyor, ama
işaretçinin gösterdiği yer çağıranın kendi yerel değişkeni — tablo bu değişkenden habersiz.
Bu dersteki `karma_bul` her zaman bir yapı ataması (`*hedef = d->deger`) yaptığı için
sınıf hep **kopya**; aynı imzayla yazılmış bir işlev, eğer `hedef`'e tablo içindeki
düğümün adresini yazsaydı (`hedef` bir `struct Kayit **` olsaydı), sonuç **ortak**
olurdu. Bu fark yalnız gövde okunarak ya da (bu derste yapıldığı gibi) kaynağı sonradan
değiştirip kopyanın bunu görüp görmediği ölçülerek anlaşılabiliyor.

#### Özet

- Karma tablo sabit sayıda kovaya dağılıyor; her kova bir zincir, çakışan anahtarlar
  aynı zincirde `anahtar` alanına bakılarak ayırt ediliyor.
- `karma_bul` bir yapı ataması (`*hedef = d->deger`) ile kaydın **kopyasını** üretiyor;
  kopya üzerindeki değişiklik tabloya hiç yansımıyor — sınıf **kopya**.
- Bir kopya, alındığı andaki değeri taşımaya devam ediyor; kaynak sonradan değişse bile
  önceden alınmış bir kopya bunu görmüyor.
- **Kopya**'nın **ortak**'tan farkı: ortakta paylaşılan nesne tek, kopyada iki bağımsız
  nesne var; **aktarım**'dan farkı: aktarımda okunan yer boşalıyor, kopyada tablo aynı
  kaydı tekrar tekrar (güncel hâliyle) verebiliyor.
- Bir bulma işlevinin döndürdüğü değerin kopya mı paylaşılan bir nesne mi olduğu
  imzada görünmüyor; yalnız gövde ya da ölçümle anlaşılıyor.
- Anahtarı değerle değil işaretçiyle tutmak sınıfı **ortak**'a çeviriyor: kova yerleşimi
  ekleme anındaki değere göre sabitleniyor ama karşılaştırma canlı belleği okuyor —
  dışarıdaki değişken değişirse kayıt hiçbir anahtarla erişilemez hâle gelebiliyor.

#### Sonraki Adım

Bu üç ders bağlı yapı, arabellek ve tablo üzerinden üç ayrı sınıfı (ortak, aktarım,
kopya) ölçtü. Bir sonraki ders, Nesne Benzeri Tasarım, C'nin sınıfı olmayan bir dilde bir
"nesne" izlenimini nasıl kurduğunu — işlev işaretçileri ve opak yapılarla — ele alıyor ve
bu üç sınıfın aynı nesne arayüzü içinde nasıl bir arada bulunabildiğini gösteriyor.

### Nesne Benzeri Tasarım

Source: https://academia.sh/tr/kurslar/c-sistem-programlama/nesne-benzeri-tasarim

Opak bir yapı ve yapı içi işlev işaretçileriyle kurulan nesne benzeri arayüzlerde, iki tutamacın aynı nesneyi paylaştığının ve bir metot çağrısının da bu paylaşımı değiştirmediğinin ölçümü.

C'nin sınıfı yok, ama bir "nesne" izlenimi iki araçla kurulabiliyor: bir **opak yapı**
(iç alanları yalnız tanımlandığı `.c` dosyasında görünen bir tip) ve içine gömülü **işlev
işaretçileri** (bir "metot" çağrısı gibi okunan, aslında bir veri alanı olan çağrılabilir
alanlar). Bu ders ikisini de kuruyor ve aynı soruyu soruyor: bir "nesne"ye iki tutamaçtan
erişildiğinde, birinden yapılan bir değişiklik ötekinden görünüyor mu? Yanıt yine
**ortak** — opaklık ve metot çağrısı sözdizimi, altta yatan paylaşımı değiştirmiyor,
yalnız onu okuma biçimini değiştiriyor.

#### Opak Yapı: Bilgi Gizleme ile Bir Nesne Arayüzü

`struct Nesne`'nin tanımı yalnız `nesne.c` içinde; `nesne.h`'de yalnız bir **ileri
bildirim** (`struct Nesne;`) var. Bu, başlığı dahil eden bir dosyanın `deger` alanına
doğrudan erişemeyeceği, yalnız `nesne_ayarla`/`nesne_oku` işlevleri üzerinden
konuşabileceği anlamına geliyor — nesnenin "genel arayüzü" bu iki işlev.

```c
// nesne.h — opak nesne tipi ve genel arayuzu
#ifndef NESNE_H
#define NESNE_H

struct Nesne;

struct Nesne *nesne_olustur(int baslangic);
void nesne_ayarla(struct Nesne *n, int deger);
int nesne_oku(const struct Nesne *n);
void nesne_serbest(struct Nesne *n);

#endif
```

```c
// nesne.c — opak tipin gerceklemesi
#include <stdlib.h>
#include "nesne.h"

struct Nesne {
    int deger;
};

struct Nesne *nesne_olustur(int baslangic) {
    struct Nesne *n = malloc(sizeof *n);
    if (n == NULL) {
        return NULL;
    }
    n->deger = baslangic;
    return n;
}

void nesne_ayarla(struct Nesne *n, int deger) {
    n->deger = deger;
}

int nesne_oku(const struct Nesne *n) {
    return n->deger;
}

void nesne_serbest(struct Nesne *n) {
    free(n);
}
```

`nesne_serbest`'in ne zaman çağrılacağı ayrı bir soru — önceki kursun (**C'de Bellek ve
İşaretçiler**, K02) sorduğu "bu nesneyi kim bırakacak" sorusu, burada da `nesne_olustur`'u
çağıranın üstlendiği bir sözleşme. Bu ders o soruyu değil, iki tutamacın aynı nesneyi
paylaşıp paylaşmadığını ölçüyor.

#### İki Tutamacın Aynı Nesneyi Paylaşması

`n` ve `ikinci_tutamac` aynı `struct Nesne *` değerini taşıyor — ikisi de aynı nesneye
işaret ediyor. Opak tip yalnız `deger` alanına **doğrudan** erişimi engelliyor;
`nesne_ayarla`/`nesne_oku` üzerinden erişim hâlâ aynı nesneye ulaşıyor.

```c
// iki_tutamac.c — ayni nesneye iki tutamactan erisim
#include <stdio.h>
#include "nesne.h"

int main(void) {
    struct Nesne *n = nesne_olustur(1);
    struct Nesne *ikinci_tutamac = n;

    printf("%s %d\n", "olusturuldugundaki deger", nesne_oku(n));

    nesne_ayarla(ikinci_tutamac, 42);
    printf("%s %d\n", "ikinci tutamactan ayarlanan degerin n uzerinden okunmasi", nesne_oku(n));
    printf("%s %d\n", "n ile ikinci_tutamac ayni nesneyi mi gosteriyor", (int)(n == ikinci_tutamac));

    nesne_serbest(n);
    return 0;
}
```

```
olusturuldugundaki deger 1
ikinci tutamactan ayarlanan degerin n uzerinden okunmasi 42
n ile ikinci_tutamac ayni nesneyi mi gosteriyor 1
```

**VY10.** `ikinci_tutamac = n` ataması bir nesne kopyalamıyor, bir **işaretçiyi**
kopyalıyor — sonuçta ikisi de aynı `malloc` bloğunun adresini taşıyor, tıpkı bağlı
listedeki `ikinci = bas->sonraki` atamasında olduğu gibi (Bağlı Yapılar dersi).
`nesne_ayarla(ikinci_tutamac, 42)` çağrısı `deger` alanını değiştiriyor ve bu değişiklik
`nesne_oku(n)` ile okunuyor: **42**. Opak tip burada hiçbir şeyi değiştirmiyor — dışarıdan
`n->deger` yazılamaz olması, `n` ile `ikinci_tutamac`'ın aynı nesneyi paylaştığı gerçeğini
gizlemiyor, yalnız o paylaşıma nasıl erişileceğini (yalnız işlevler üzerinden) sınırlıyor.
Son satır bunu adres basmadan doğruluyor: `n == ikinci_tutamac` karşılaştırması **1**.
Sınıf **ortak**.

#### İşlev İşaretçileriyle Metot Çağrısı İzlenimi

İkinci yaklaşım bilgiyi gizlemiyor, "metotları" doğrudan yapının içine gömüyor: `artir` ve
`deger_oku` alanları birer işlev işaretçisi ve `sayac_nesnesi_hazirla` onları somut
işlevlere bağlıyor. Çağıran taraf `s.artir(&s, 5)` yazdığında bu, C++'taki `s.artir(5)`
çağrısına benzeyen bir sözdizimi üretiyor — ama `artir` bir metot değil, bir veri alanı.

```c
// sayac_nesne.h — yapi ici islev gostergeleriyle metot cagrisi izlenimi
#ifndef SAYAC_NESNE_H
#define SAYAC_NESNE_H

struct SayacNesnesi {
    int adet;
    void (*artir)(struct SayacNesnesi *, int);
    int (*deger_oku)(const struct SayacNesnesi *);
};

void sayac_nesnesi_hazirla(struct SayacNesnesi *s, int baslangic);

#endif
```

```c
// sayac_nesne.c — islev gostergelerinin baglanmasi
#include "sayac_nesne.h"

static void sayac_artir(struct SayacNesnesi *s, int miktar) {
    s->adet = s->adet + miktar;
}

static int sayac_deger_oku(const struct SayacNesnesi *s) {
    return s->adet;
}

void sayac_nesnesi_hazirla(struct SayacNesnesi *s, int baslangic) {
    s->adet = baslangic;
    s->artir = sayac_artir;
    s->deger_oku = sayac_deger_oku;
}
```

```c
// yontem_cagrisi.c — iki tutamacin ayni yapi ici islevleri cagirmasi
#include <stdio.h>
#include "sayac_nesne.h"

int main(void) {
    struct SayacNesnesi s;
    sayac_nesnesi_hazirla(&s, 0);

    struct SayacNesnesi *birinci_tutamac = &s;
    struct SayacNesnesi *ikinci_tutamac = &s;

    birinci_tutamac->artir(birinci_tutamac, 5);
    printf("%s %d\n", "birinci tutamacla artirdiktan sonra ikinciden okunan",
           ikinci_tutamac->deger_oku(ikinci_tutamac));

    ikinci_tutamac->artir(ikinci_tutamac, 3);
    printf("%s %d\n", "iki tutamacla art arda artis sonrasi deger", s.deger_oku(&s));

    return 0;
}
```

```
birinci tutamacla artirdiktan sonra ikinciden okunan 5
iki tutamacla art arda artis sonrasi deger 8
```

**VY11.** `birinci_tutamac` ve `ikinci_tutamac` ikisi de `&s`'i taşıyor — aynı yerel
değişkenin adresi, tıpkı bir önceki ölçümdeki `n`/`ikinci_tutamac` çiftinde olduğu gibi.
`birinci_tutamac->artir(birinci_tutamac, 5)` çağrısı `s.adet`'i 0'dan 5'e çıkarıyor ve
`ikinci_tutamac->deger_oku(ikinci_tutamac)` bunu görüyor: **5**. `s.artir` ile
`s.deger_oku` birer işlev işaretçisi olduğu için `birinci_tutamac->artir` ile
`ikinci_tutamac->artir` de aynı işaretçiyi okuyor — çağrılan işlev ikisinde de
`sayac_artir`. Metot çağrısı gibi görünen sözdizimi, `s.adet`'e erişimi hiç
değiştirmiyor; `artir` ve `deger_oku` sadece hangi işlevin çağrılacağını **veri olarak**
taşıyor. Sınıf yine **ortak**: iki tutamaç aynı yapıyı paylaşıyor, aradaki fark yalnız
erişim sözdiziminde.

#### Paylaşılan Yöntem Tablosu

`SayacNesnesi`'nin her örneği kendi `artir`/`deger_oku` alanlarını taşıyor — aynı
işlevlere işaret etseler de, her örnek bu iki işaretçinin **kendi kopyasını** saklıyor.
Alternatif bir tasarım örnekleri tek bir **paylaşılan** yöntem tablosuna bağlıyor: her
örnek yalnız tabloya bir işaretçi taşıyor, tablonun kendisi tüm örnekler arasında tek.

```c
// sayac_vtable.h — paylasilan yontem tablosuyla nesne tasarimi
#ifndef SAYAC_VTABLE_H
#define SAYAC_VTABLE_H

struct SayacYonetici;

struct SayacYontemleri {
    void (*artir)(struct SayacYonetici *, int);
    int (*deger_oku)(const struct SayacYonetici *);
};

struct SayacYonetici {
    int adet;
    const struct SayacYontemleri *yontemler;
};

extern const struct SayacYontemleri SAYAC_YONTEMLERI;

void sayac_yonetici_hazirla(struct SayacYonetici *s, int baslangic);

#endif
```

```c
// sayac_vtable.c — tek paylasilan yontem tablosunun tanimi
#include "sayac_vtable.h"

static void sayac_artir_v(struct SayacYonetici *s, int miktar) {
    s->adet = s->adet + miktar;
}

static int sayac_deger_oku_v(const struct SayacYonetici *s) {
    return s->adet;
}

const struct SayacYontemleri SAYAC_YONTEMLERI = { sayac_artir_v, sayac_deger_oku_v };

void sayac_yonetici_hazirla(struct SayacYonetici *s, int baslangic) {
    s->adet = baslangic;
    s->yontemler = &SAYAC_YONTEMLERI;
}
```

```c
// paylasilan_tablo.c — iki ornegin adeti ayri, yontem tablosu ortak mi
#include <stdio.h>
#include "sayac_vtable.h"

int main(void) {
    struct SayacYonetici birinci;
    struct SayacYonetici ikinci;
    sayac_yonetici_hazirla(&birinci, 0);
    sayac_yonetici_hazirla(&ikinci, 100);

    birinci.yontemler->artir(&birinci, 5);
    ikinci.yontemler->artir(&ikinci, 5);

    printf("%s %d\n", "birinci ornegin adet degeri", birinci.yontemler->deger_oku(&birinci));
    printf("%s %d\n", "ikinci ornegin adet degeri", ikinci.yontemler->deger_oku(&ikinci));
    printf("%s %d\n", "iki ornegin yontem tablosu ayni adresi mi gosteriyor",
           (int)(birinci.yontemler == ikinci.yontemler));

    return 0;
}
```

```
birinci ornegin adet degeri 5
ikinci ornegin adet degeri 105
iki ornegin yontem tablosu ayni adresi mi gosteriyor 1
```

**VY17.** `birinci.adet` ile `ikinci.adet` birbirinden bağımsız — her örnek kendi `int`
alanını taşıyor, biri değişince öteki etkilenmiyor. Ama `birinci.yontemler` ile
`ikinci.yontemler` **aynı** adresi taşıyor: `&SAYAC_YONTEMLERI`, dosya kapsamında tek bir
kere tanımlanmış `const` bir nesne. `SayacNesnesi` tasarımında her örnek `artir` ve
`deger_oku` alanlarının **kendi kopyasını** tutuyordu (aynı işleve işaret etseler de, iki
ayrı bellek konumunda); burada örnekler tablonun **kendisini** değil, ona giden tek bir
işaretçiyi paylaşıyor. Bir yapı içinde iki farklı sınıf aynı anda görülebiliyor: `adet`
alanı için örnekler birbirinden **kopya** gibi bağımsız, `yontemler`'in gösterdiği tablo
için ise **ortak** — tablo tek bir nesne ve tüm örnekler ona aynı adresle bağlanıyor.

#### Bir Kopyalama İşleviyle Bağımsız Bir Nesne Üretmek

Şimdiye kadarki iki örnek (tutamaç kopyalama, paylaşılan yöntem tablosu) hep **ortak**
sınıfını üretti: bir işaretçi kopyalandığında ya da paylaşıldığında, altta yatan yapı
tek kaldı. Ama `nesne.h`'nin genel arayüzü zaten bağımsız bir nesne üretmek için yeterli
— `nesne_oku` ile mevcut değeri okuyup `nesne_olustur` ile o değerden yeni bir nesne
kurmak, opak tipin iç alanına hiç dokunmadan bir **derin kopya** üretiyor.

```c
// nesne_klon.h — opak nesnenin genel arayuzuyle bagimsiz kopyasini kuran islev
#ifndef NESNE_KLON_H
#define NESNE_KLON_H

#include "nesne.h"

struct Nesne *nesne_klonla(const struct Nesne *kaynak);

#endif
```

```c
// nesne_klon.c — klonlama yalniz genel arayuzu kullaniyor, ic alana dokunmuyor
#include "nesne_klon.h"

struct Nesne *nesne_klonla(const struct Nesne *kaynak) {
    return nesne_olustur(nesne_oku(kaynak));
}
```

```c
// nesne_klon_demo.c — klon n'den bagimsiz, n degisince klon etkilenmiyor
#include <stdio.h>
#include "nesne_klon.h"

int main(void) {
    struct Nesne *n = nesne_olustur(7);
    struct Nesne *klon = nesne_klonla(n);

    printf("%s %d\n", "klonun baslangictaki degeri", nesne_oku(klon));

    nesne_ayarla(n, 99);
    printf("%s %d\n", "n degistirildikten sonra klonun degeri", nesne_oku(klon));
    printf("%s %d\n", "n ile klon ayni nesneyi mi gosteriyor", (int)(n == klon));

    nesne_serbest(n);
    nesne_serbest(klon);
    return 0;
}
```

```
klonun baslangictaki degeri 7
n degistirildikten sonra klonun degeri 7
n ile klon ayni nesneyi mi gosteriyor 0
```

**VY19.** `nesne_klonla`, `n`'nin işaret ettiği yapıya hiç dokunmuyor — yalnız
`nesne_oku(kaynak)` ile mevcut değeri okuyup `nesne_olustur` ile **yeni bir `malloc`
bloğu** kuruyor. Sonuçta `klon`, `n`'den ayrı bir adres taşıyor: son satır bunu
`n == klon` karşılaştırmasının **0** dönmesiyle doğruluyor. `nesne_ayarla(n, 99)`
çağrısı yalnız `n`'nin işaret ettiği bloğu değiştiriyor; `klon`'un işaret ettiği blok
etkilenmiyor, `nesne_oku(klon)` hâlâ **7** dönüyor. Bu, dersin ilk iki örneğinin tam
tersi: `ikinci_tutamac = n` bir işaretçiyi kopyalayıp aynı yapıyı paylaştırıyordu
(**ortak**), `nesne_klonla` ise genel arayüzü kullanarak **yeni bir yapı** kuruyor ve
sonucu **kopya** sınıfına taşıyor — iki nesne artık birbirinden bağımsız, biri
değişince öteki değişmiyor. Fark kodun biçiminde değil: ikisi de işaretçi atıyor,
ikisi de opak tipin arkasında çalışıyor; fark, sağ taraftaki ifadenin **var olan bir
adresi mi taşıdığı** yoksa **yeni bir ayırmadan mı geldiği**.

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

Bir "nesne" arayüzünün biçimi — opak bir tip mi, yapı içi işlev işaretçileri mi, sıradan
işlevler mi — altta yatan paylaşımı belirlemiyor. `nesne_ayarla(ikinci_tutamac, 42)` ile
`birinci_tutamac->artir(birinci_tutamac, 5)` farklı görünüyor (biri sıradan bir işlev
çağrısı, öteki yapı alanı üzerinden bir çağrı gibi okunuyor), ama ikisi de aynı ilkeye
dayanıyor: bir işaretçiyle erişilen bir yapıyı değiştirmek, o yapıya erişen her işaretçiden
görünür. Bir arayüzün "nesne benzeri" görünmesi — bilgi gizleme, metot çağrısı sözdizimi —
paylaşımı gizlemiyor ya da ortadan kaldırmıyor, yalnız paylaşılan yapıya nasıl
erişileceğini biçimlendiriyor. Paylaşımın kendisi hâlâ yalnız işaretçilerin eşitliğiyle
(`n == ikinci_tutamac`, `birinci_tutamac == ikinci_tutamac`) ölçülebiliyor.

#### Özet

- Bir opak tip (`struct Nesne;` ileri bildirimi) iç alanlara doğrudan erişimi
  engelliyor, ama nesneyi işaret eden iki tutamaç arasındaki paylaşımı değiştirmiyor;
  yapı içine gömülü işlev işaretçileri de (`artir`, `deger_oku`) bir metot çağrısı
  sözdizimi veriyor ama birer veri alanı olarak kalıyor. Paylaşım yalnız işaretçi
  eşitliğiyle ölçülebiliyor.
- İki tutamaç aynı nesneyi/yapıyı gösterdiğinde, birinden yapılan bir değişiklik
  ötekinden (hangi arayüz biçimi kullanılırsa kullanılsın) görünüyor — sınıf **ortak**.
- Bir nesneyi **kim bırakacağı** (K02'nin sorusu) ile bir değişikliğin **kim
  tarafından görüleceği** (bu dersin sorusu) ayrı eksenler.
- Örneklerin kendi işlev işaretçilerini taşıması ile tek bir paylaşılan tabloya
  işaretçiyle bağlanması farklı sonuçlar veriyor: birincisinde her örnek kendi kopyasını
  tutuyor, ikincisinde tablo tek ve tüm örnekler ona aynı adresle erişiyor.
- Genel arayüzü (yalnız `nesne_olustur`/`nesne_oku`) kullanarak yeni bir nesne kurmak
  işaretçi kopyalamaktan farklı: sonuç **kopya** sınıfına giriyor, iki nesne birbirinden
  bağımsız kalıyor.

#### Sonraki Adım

Bu dersin iki örneğinde de kaynak yönetimi basitti: tek bir `nesne_serbest` çağrısı ya da
hiç ayırma. Bir sonraki ders, Kaynak Temizleme Deyimleri, birden fazla kaynağın (birden
fazla ayırma, birden fazla açılan tutamaç) bir işlev içinde sırayla açılıp hata durumunda
doğru sırayla kapatılması gereken durumları ölçüyor — orada sınıf **gözlenmeyen**: bir
kaynak, hiçbir okuyucuya ulaşmadan, hata yolunda kapatılıp kayboluyor.

### Kaynak Temizleme Deyimleri

Source: https://academia.sh/tr/kurslar/c-sistem-programlama/kaynak-temizleme-deyimleri

Birden fazla kaynağın sırayla açıldığı bir işlevde, hata yolunda hiçbir okuyucuya ulaşmadan kapatılıp kaybolan kaynağın ölçümü.

Bu ders serideki son ölçüm sınıfını kapatıyor: **gözlenmeyen**. Önceki derslerde bir
değişiklik ya yazarında kaldı (kopya), ya paylaşılan bir nesneden herkese göründü (ortak),
ya da bir aracıdan geçip bir kez okundu (aktarım). Burada dördüncü durum: bir kaynak
açılıyor, doldurulmadan ya da okunmadan bir hata yüzünden kapatılıyor — hiçbir okuyucuya
**hiç ulaşmıyor**. Ölçülen şey bir değerin nereye ulaştığı değil, hangi kaynağın hiç
ulaşmadan kaybolduğu.

#### Sırayla Açılan Kaynaklar, Ters Sırayla Kapanan Kaynaklar

`Depo` iki ayrı ayırma taşıyor: bir `int` dizisi ve bir etiket dizesi. İkisi de
`depo_ac` içinde sırayla açılıyor; biri başarısız olursa o ana kadar açılmış olanlar
**ters sırayla** kapatılıyor — son açılan ilk kapanıyor.

```c
// depo.h — birden fazla kaynak tasiyan bir yapi
#ifndef DEPO_H
#define DEPO_H

struct Depo {
    int *degerler;
    char *etiket;
    int eleman_sayisi;
};

int depo_ac(struct Depo *d, int eleman_sayisi, const char *etiket);
void depo_kapat(struct Depo *d);
int *depo_veri_devret(struct Depo *d);

#endif
```

```c
// depo.c — sirali acilis, ters sirali kapanis
#include <stdlib.h>
#include <string.h>
#include "depo.h"

int depo_ac(struct Depo *d, int eleman_sayisi, const char *etiket) {
    d->degerler = NULL;
    d->etiket = NULL;
    d->eleman_sayisi = 0;

    d->degerler = malloc((size_t)eleman_sayisi * sizeof *d->degerler);
    if (d->degerler == NULL) {
        return 0;
    }

    d->etiket = malloc(strlen(etiket) + 1);
    if (d->etiket == NULL) {
        free(d->degerler);
        d->degerler = NULL;
        return 0;
    }
    strcpy(d->etiket, etiket);

    d->eleman_sayisi = eleman_sayisi;
    return 1;
}

void depo_kapat(struct Depo *d) {
    free(d->etiket);
    free(d->degerler);
    d->etiket = NULL;
    d->degerler = NULL;
}

int *depo_veri_devret(struct Depo *d) {
    int *tasinan = d->degerler;
    d->degerler = NULL;
    d->eleman_sayisi = 0;
    return tasinan;
}
```

Bu iki kaynağın açılıp kapanması, önceki kursun (**C'de Bellek ve İşaretçiler**) sorduğu
"kim bırakacak" sorusuna aynı yanıtı veriyor: `depo_ac`'ı çağıran, `depo_kapat`'ı da
çağırmakla yükümlü. Bu ders o sözleşmeyi değil, `depo_ac` yarıda kaldığında `etiket`
alanına hiç yazılmamış olmasının nasıl ölçüleceğini konu ediyor.

#### Bir Kaynağın Hiçbir Okuyucuya Ulaşmadan Kapanması

`depo_ac` içindeki `(size_t)eleman_sayisi * sizeof *d->degerler` ifadesi, negatif bir
`eleman_sayisi` verildiğinde işaretsiz türe dönüşüp devasa bir sayıya sarıyor — bu boyutu
karşılayabilecek bir `malloc` yok, çağrı kesin olarak `NULL` dönüyor. `degerler` ayrılamıyor,
`etiket` hiç açılmıyor, `depo_ac` `0` döndürüyor.

```c
// basarisiz_acilis.c — ilk kaynagin acilamamasi, ikincinin hic acilmamasi
#include <stdio.h>
#include "depo.h"

int main(void) {
    struct Depo d;
    int sonuc = depo_ac(&d, -1, "olcum");

    printf("%s %d\n", "depo_ac sonucu", sonuc);
    printf("%s %d\n", "etiket hic acilmadigi icin NULL mi", (int)(d.etiket == NULL));
    printf("%s %d\n", "degerler de acilamadigi icin NULL mi", (int)(d.degerler == NULL));

    depo_kapat(&d);
    return 0;
}
```

```
depo_ac sonucu 0
etiket hic acilmadigi icin NULL mi 1
degerler de acilamadigi icin NULL mi 1
```

**VY12.** `eleman_sayisi` olarak verilen `-1`, `(size_t)eleman_sayisi` ile işaretsiz türe
dönüşünce çok büyük bir değere sarıyor; bu değerin `sizeof(int)` ile çarpımı `malloc`'un
hiçbir ortamda karşılayamayacağı bir boyut istiyor — çağrı `NULL` dönüyor ve `depo_ac`
ilk `if` dalında **hemen** çıkıyor. `etiket` alanı için `malloc` hiç çağrılmıyor; `d.etiket` fonksiyon
başında atanan `NULL` değerinde kalıyor. `etiket` kaynağı — bir okuyucuya ulaşmadan,
bir değer taşımadan — **hiç var olmadan** kayboluyor. Sınıf **gözlenmeyen**: önceki
derslerdeki kopya/ortak/aktarım sınıflarının hepsi bir değerin bir yere ulaştığını
varsayıyordu; burada hiçbir yere ulaşmıyor. `depo_kapat` yine de çağrılıyor —
`free(NULL)` tanımlı ve zararsız, bu yüzden temizleme çağrısı koşulsuz yapılabiliyor.

#### İkinci Kaynak Açılırken Birincinin Geri Alınması

Şimdi ilk kaynak (`degerler`) başarıyla açılıyor ama ikincisi (`etiket`) için ayırma
başarısız olsaydı, `depo_ac` birinciyi de geri almak zorunda. Bunu doğrudan gözlemlemek
için (gerçek bir `malloc` başarısızlığı bu ortamda tetiklenemediğinden) `depo_ac`'ın geri
alma dalını ayrı bir işlevle, `depo_ac_basarisiz_ikinci` ile yeniden üretiyoruz — aynı
geri alma deseni, ikinci ayırma noktasında zorla başarısız kılınmış hâliyle.

```c
// depo_basarisiz_ikinci.c — ikinci kaynak acilirken birincinin geri alinmasi
#include <stdlib.h>
#include "depo.h"

int depo_ac_basarisiz_ikinci(struct Depo *d, int eleman_sayisi) {
    d->degerler = NULL;
    d->etiket = NULL;
    d->eleman_sayisi = 0;

    d->degerler = malloc((size_t)eleman_sayisi * sizeof *d->degerler);
    if (d->degerler == NULL) {
        return 0;
    }

    /* ikinci ayirma burada kasitli olarak basarisiz kabul ediliyor */
    free(d->degerler);
    d->degerler = NULL;
    return 0;
}
```

```c
// ikinci_geri_alma.c — birinci acilan kaynagin geri alindigi cagri
#include <stdio.h>
#include "depo.h"

int depo_ac_basarisiz_ikinci(struct Depo *d, int eleman_sayisi);

int main(void) {
    struct Depo d;
    int sonuc = depo_ac_basarisiz_ikinci(&d, 4);

    printf("%s %d\n", "depo_ac_basarisiz_ikinci sonucu", sonuc);
    printf("%s %d\n", "birinci kaynak geri alindigi icin NULL mi", (int)(d.degerler == NULL));
    printf("%s %d\n", "ikinci kaynak hic acilmadigi icin NULL mi", (int)(d.etiket == NULL));

    return 0;
}
```

```
depo_ac_basarisiz_ikinci sonucu 0
birinci kaynak geri alindigi icin NULL mi 1
ikinci kaynak hic acilmadigi icin NULL mi 1
```

**VY13.** `degerler` burada gerçekten açılıyor (4 elemanlık bir dizi için `malloc`
başarıyla dönüyor), ama ikinci kaynağın açılması başarısız kabul edildiği an
`free(d->degerler)` ile **geri alınıyor**. `d.degerler`'in `NULL` olması, kaynağın
açılıp da hiçbir okuyucuya ulaşmadan kapandığını gösteriyor — tıpkı Halka Arabellek ve
Kuyruklar dersindeki bir aktarımın tek okuyuculuğu gibi, ama burada okuyucu hiç yok; değer
üretilip aynı işlev içinde imha ediliyor. Bu desen, önceki kursun
**Hata Yönetimi Kalıpları** dersinde işlenen `goto temizle` deyiminin yaptığı işin aynısı —
orada tek bir çıkış noktasından geriye doğru sırayla kapatma `goto` ile kuruluyordu, burada
aynı sıralama açık `if`/`free` çağrılarıyla elle yazıldı. Bu ders o deyimi yeniden
öğretmiyor, yalnız ürettiği sonucu — kaynağın gözlenmeden kaybolduğunu — ölçüyor.

#### exit, _exit ve atexit Sırası

Şimdiye kadarki geri alma elle yazılan `if`/`free` çağrılarıyla kuruldu. C'nin
`atexit` işlevi bir başka yol sunuyor: bir işleyiciyi "süreç normal şekilde
sonlanırken çalıştır" diye kaydediyor. Ama bu kayıt, sürecin **nasıl** sonlandığına
bağlı — `exit` ile `_exit` arasındaki fark burada gözlenmeyen sınıfının bir başka
görünümünü ortaya çıkarıyor.

```c
// atexit_sirasi.c — exit atexit isleyicilerini kayit sirasinin tersiyle calistirir
#include <stdio.h>
#include <stdlib.h>

static void temizleyici_a(void) {
    printf("%s\n", "temizleyici_a calisti");
}

static void temizleyici_b(void) {
    printf("%s\n", "temizleyici_b calisti");
}

int main(void) {
    atexit(temizleyici_a);
    atexit(temizleyici_b);

    printf("%s\n", "main govdesi bitti");
    exit(0);
}
```

```
main govdesi bitti
temizleyici_b calisti
temizleyici_a calisti
```

```c
// exit_ve_atexit_atlanmasi.c — _exit atexit isleyicilerini hic calistirmaz
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static void hic_calismayan(void) {
    printf("%s\n", "bu satir hic basilmiyor");
}

int main(void) {
    atexit(hic_calismayan);

    printf("%s\n", "_exit cagrilmadan hemen once");
    fflush(stdout);
    _exit(0);
}
```

```
_exit cagrilmadan hemen once
```

**VY18.** İlk programda `atexit(temizleyici_a)` önce, `atexit(temizleyici_b)` sonra
kaydediliyor; `exit(0)` çağrıldığında ikisi **kayıt sırasının tersiyle** çalışıyor —
önce `temizleyici_b`, sonra `temizleyici_a`. Bu, `depo_ac`'ın ters sırayla geri alma
düzenini (bu dersin ilk bölümü) `atexit` üzerinden otomatikleştirilmiş hâliyle
tekrarlıyor. İkinci programda ise `hic_calismayan` da `atexit` ile kaydediliyor, ama
süreç `exit` ile değil **`_exit`** ile sonlanıyor — POSIX'in `_exit` işlevi hiçbir
`atexit` işleyicisini çalıştırmadan süreci hemen kapatıyor. Kayıtlı işleyici çağrılmadığı
için `"bu satir hic basilmiyor"` hiç basılmıyor; çıktı yalnız `_exit`'ten önceki satırdan
ibaret. Bu, gözlenmeyen sınıfının işlev düzeyindeki hâli: `hic_calismayan` bir okuyucuya
(bir çıktıya) ulaşmak üzere **kayıtlıydı**, ama hiç çalıştırılmadı. `fflush(stdout)`
satırı da ayrı bir noktayı gösteriyor: `_exit`, `exit`'in aksine, tamponlanmış
`stdio` çıktısını boşaltmıyor — bu satır olmasaydı `"_exit cagrilmadan hemen once"`
satırının da kaybolma ihtimali olurdu, çünkü tampon `_exit` ile birlikte hiç
boşalmadan yok oluyor.

#### Kaynağın Bir Değişkene Devredilmesi

`depo_kapat`, `d.degerler`'in sahipliğini hep `Depo` yapısında tutuyordu. Sahiplik
başka bir değişkene **devredilebilir** de: `depo_veri_devret`, diziyi `d`'den koparıp
çağırana veriyor ve `d.degerler`'i `NULL` bırakıyor — `d` artık o diziye erişemiyor,
erişim tek yoldan yeni sahibe geçti.

```c
// veri_devri.c — depo icindeki dizinin sahipligi baska bir degiskene deveriliyor
#include <stdio.h>
#include <stdlib.h>
#include "depo.h"

int main(void) {
    struct Depo d;
    depo_ac(&d, 3, "gecici");

    int *devralinan = depo_veri_devret(&d);
    devralinan[0] = 10;

    printf("%s %d\n", "devirden sonra depo.degerler NULL mi", (int)(d.degerler == NULL));
    printf("%s %d\n", "devralinan uzerinden yazilan deger", devralinan[0]);

    free(devralinan);
    depo_kapat(&d);
    return 0;
}
```

```
devirden sonra depo.degerler NULL mi 1
devralinan uzerinden yazilan deger 10
```

**VY20.** `depo_veri_devret` çağrılmadan önce diziye tek erişim yolu `d.degerler`'di.
Çağrıdan sonra `tasinan` (çağıran tarafta `devralinan`) diziyi taşıyor, `d->degerler`
ise `NULL`'a ayarlanıyor — aynı anda iki erişim yolu yok, biri kapanırken öteki açılıyor.
Bu, dersin geri kalanındaki **gözlenmeyen** örneklerinden farklı: orada kaynak hiçbir
okuyucuya ulaşmadan kayboluyordu, burada kaynak tam olarak **bir** okuyucuya (yeni
sahibine) ulaşıyor ve eski yoldan erişilemez hâle geliyor — sınıf **aktarım**. Devirden
sonra `depo_kapat(&d)` çağrısının `free(d.degerler)` satırı güvenli: `d.degerler` zaten
`NULL`, `free(NULL)` bir şey yapmıyor; diziyi serbest bırakma sorumluluğu artık
`devralinan`'ı tutan koda ait, bu da ayrı bir `free(devralinan)` çağrısıyla görülüyor.

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

`gözlenmeyen` sınıfı, bir değerin *yanlış* yere ulaştığını değil, **hiçbir yere**
ulaşmadığını ölçüyor. `d.etiket == NULL` ya da `d.degerler == NULL` kontrolleri bunun
kanıtı: kaynak bir okuyucu tarafından görülmeden, atanmadan, kopyalanmadan devre dışı
kaldı. Bu, önceki dört dersteki ölçümlerden temelde farklı — kopya/ortak/aktarım hep "bir
değer bir yere ulaştı, oraya nasıl ulaştığını ölçüyoruz" diyordu; burada soru "bir kaynak
ulaşmadan kayboldu, bunu nasıl doğrularız." Doğrulama aracı da aynı kalıyor: işaretçi
karşılaştırması (`== NULL`), adres ya da içerik basılmadan.

#### Özet

- Birden fazla kaynak sırayla açılıyorsa, hata yolunda **ters sırayla** geri alınmaları
  gerekiyor — son açılan ilk kapanan.
- Bir kaynağın açılışı başarısız olduğunda o kaynak hiçbir okuyucuya ulaşmıyor
  (`NULL` kalması bunun ölçülebilir kanıtı); ikinci bir kaynağın açılışı başarısız
  olduğunda, o ana kadar açılmış birinci kaynak da aynı şekilde geri alınıyor. Bu
  desen, Hata Yönetimi Kalıpları dersindeki `goto temizle` deyiminin ürettiği sonuçla aynı; ders o
  deyimi öğretmiyor, yalnız sonucunu ölçüyor.
- Sınıf **gözlenmeyen**: bir değerin nereye ulaştığı değil, hangi kaynağın hiç
  ulaşmadan kaybolduğu ölçülüyor.
- `atexit` ile kaydedilen işleyiciler `exit` çağrıldığında kayıt sırasının tersiyle
  çalışıyor; `_exit` ise hiçbirini çalıştırmadan süreci kapatıyor ve tamponlanmış
  `stdio` çıktısını da boşaltmıyor.
- Bir kaynağın sahipliği başka bir değişkene devredildiğinde eski yol `NULL`'a
  ayarlanıyor, tek erişim yeni sahipte kalıyor — sınıf **aktarım**, gözlenmeyenden
  farklı olarak kaynak tam olarak bir okuyucuya ulaşıyor.

#### Sonraki Adım

Bu beş ders, "bu değişikliği kim görüyor" sorusunu dört sınıf üzerinden ölçtü: kopya,
ortak, aktarım, gözlenmeyen. Sıradaki konu bu sınıfları tek tek yapılarda değil, bir
arada — birden fazla veri yapısının aynı işlev içinde birlikte kullanıldığı, hangi
değişikliğin hangi sınıfa girdiğinin ayrıştırılması gereken bileşik durumlarda ele alıyor.

## Proje Altyapısı

Bağımlılık grafiğiyle derleme sistemleri, statik ve dinamik kütüphane üretimi, yalıtılmış birim test ve sahteleme, kesme noktası ve gözetleme noktasıyla hata ayıklama, ve eniyileme düzeylerinin gözlenebilir davranışı nasıl koruduğu.

### Derleme Sistemleri

Source: https://academia.sh/tr/kurslar/c-sistem-programlama/derleme-sistemleri

Bağımlılığı yazılmamış bir başlığın kural tabanlı bir derleme modelinde neden hâlâ eski sürümde kaldığını, zaman damgası karşılaştırmasını C içinde kurarak ölçer.

Önceki konu bir kaynağın çıkışta kime bırakıldığını ölçtü — sahibi belli bir bloğun, elden
çıkarken kimin elinde kaldığını. Bu ders aynı soruyu kaynak dosyadan derlenmiş programa
taşıyor: bir başlık dosyası değiştiğinde, o değişikliğin bağlı olduğu her hedefe gerçekten
ulaşıp ulaşmadığını. Ulaşmazsa hedef, yazan tarafın elindeki yeni sürümden habersiz, eski
halinin bir kopyasını taşımaya devam eder.

Kural tabanlı bir derleme sistemi bu kararı tek bir karşılaştırmayla verir: bir hedefin
bağımlı olduğu her girdinin zaman damgası, hedefin kendi üretildiği zaman damgasından daha
mı yeni? Öyleyse yeniden üretilir. Bu kural doğru çalışması için hedefin bağımlılık
listesinin **eksiksiz** olmasını ister — listede olmayan bir girdi, değişse bile hiç
sorulmaz.

Bu karar tek bir hedefle sınırlı kalmaz. Bir program genellikle birden çok hedeften
kurulur ve bu hedefler birbirine bağımlı olabilir: bir kaynak dosya bir başlığa, o başlık
başka bir başlığa bağımlı olabilir. Kural tabanlı sistem bu zinciri **geçişli** olarak
izler — zincirin en altındaki bir değişiklik, aradaki her hedefi tek tek yeniden üretim
listesine sokar. Zincirin ortasında bir hedefin bağımlılık listesi eksikse, o hedeften
sonraki her şey de bu eksikliği devralır; eksiklik yalnız bir hedefi değil, ona bağlı
bütün alt ağacı etkiler.

#### Zaman Damgası ve Bağımlılık Kararı

Aşağıdaki model bir derleme hedefinin durumunu tutar: en son üretildiği zaman damgası ve o
anda gömülen sürüm numarası. Karar tek bir işlevde toplanır.

```c
// hedef.h — bir derleme hedefinin bagimlilik kaydini tutan yapi
#ifndef HEDEF_H
#define HEDEF_H

struct Hedef {
    int uretim_zamani;    /* en son uretildiginde okunan zaman damgasi */
    int uretilen_surum;   /* o uretimde govdeye gomulen surum numarasi */
};

/* Bagimliligin zaman damgasi hedefin uretim zamanindan yeniyse hedef
   yeniden uretilir ve uretilen_surum baslik_surumu'ne guncellenir. Bu
   cagri hic yapilmazsa -- yani bagimlilik listeye hic yazilmamissa --
   hedef eski durumunu degistirmeden korur. */
void bagimliligi_denetle(struct Hedef *h, int baslik_zamani, int baslik_surumu);

#endif
```

Kural tek yerde yaşar; başlık zamanı, hedefin kendi zamanından yeniyse yeniden üretim
gerçekleşir.

```c
// hedef.c — karar tek kuralla veriliyor: bagimlilik hedeften daha yeni mi
#include "hedef.h"

void bagimliligi_denetle(struct Hedef *h, int baslik_zamani, int baslik_surumu) {
    if (baslik_zamani > h->uretim_zamani) {
        h->uretilen_surum = baslik_surumu;
        h->uretim_zamani = baslik_zamani;
    }
}
```

Şimdi aynı başlangıç durumundaki iki hedef karşılaştırılıyor. `yazilmis` hedefi için
`bagimliligi_denetle` başlığın **güncel** zaman damgası ve sürümüyle çağrılıyor — bağımlılık
listede yazılı, derleme sistemi başlığın değiştiğini biliyor. `yazilmamis` hedefi için bu
çağrı hiç yapılmıyor; sanki o hedefin bağımlılık listesinde başlık hiç geçmiyormuş gibi.
Başlığın kendisi ikisi için de aynı şekilde 2. sürüme çıktı; fark yalnız derleme sisteminin
bunu **bilip bilmediğinde**.

```c
// olcum.c — yazilmis/yazilmamis karari ve uc halkali zincirin karari tek kosumda
#include <stdio.h>
#include "hedef.h"

int main(void) {
    struct Hedef yazilmis = { 10, 1 };     /* uretim_zamani=10, surum=1 */
    struct Hedef yazilmamis = { 10, 1 };   /* ayni baslangic durumu */

    /* baslik gercekte 2. surume, zaman damgasi 20'ye guncellendi */
    bagimliligi_denetle(&yazilmis, 20, 2);
    /* yazilmamis icin bu satir yok -- bagimlilik listede hic gecmiyor */

    printf("yazilmis bagimlilik uretilen_surum: %d\n", yazilmis.uretilen_surum);
    printf("yazilmamis bagimlilik uretilen_surum: %d\n", yazilmamis.uretilen_surum);

    /* Uc halkali zincir: temel -> ara -> son. Tam yazimda ara, temel'e
       bagimli oldugunu bildiriyor ve kendi kaydini guncelliyor; son da
       ara'ya bagli oldugu icin guncellenmis kaydi devraliyor. Eksik
       yazimda ara, temel'e bagimliligini hic bildirmiyor; son ara'ya
       dogru bagli olsa bile devralacak yeni bir kayit bulamiyor. */
    struct Hedef ara_tam = { 10, 1 };
    struct Hedef son_tam = { 10, 1 };
    struct Hedef ara_eksik = { 10, 1 };
    struct Hedef son_eksik = { 10, 1 };

    bagimliligi_denetle(&ara_tam, 20, 2);
    bagimliligi_denetle(&son_tam, ara_tam.uretim_zamani, ara_tam.uretilen_surum);

    /* ara_eksik icin bagimliligi_denetle hic cagrilmiyor */
    bagimliligi_denetle(&son_eksik, ara_eksik.uretim_zamani, ara_eksik.uretilen_surum);

    printf("tam zincir son uretilen_surum: %d\n", son_tam.uretilen_surum);
    printf("eksik zincir son uretilen_surum: %d\n", son_eksik.uretilen_surum);
    return 0;
}
```

```text
yazilmis bagimlilik uretilen_surum: 2
yazilmamis bagimlilik uretilen_surum: 1
tam zincir son uretilen_surum: 2
eksik zincir son uretilen_surum: 1
```

**PA1.** Zaman damgaları burada tamsayı sayaçlarıyla temsil ediliyor; gerçek bir dosya
sisteminin taşıdığı takvim değeri değil. Karşılaştırmanın sonucu değişmiyor, çünkü kural
yalnız **sıralamaya** bakıyor — hangi tamsayının büyük olduğuna.

`yazilmis` hedefi başlığın yeni sürümünü gördü ve `uretilen_surum` alanı 2'ye güncellendi.
`yazilmamis` hedefi ise 1'de kaldı: başlık gerçekte değişmiş olsa da, derleme sisteminin
bağımlılık listesinde bu ilişki hiç yazılı değildi, dolayısıyla karşılaştırma hiç
çalıştırılmadı. `yazilmamis.uretilen_surum` alanı, yazan tarafın bıraktığı yeni değerden
tamamen habersiz, eski sürümün bir **kopyasını** taşımaya devam ediyor. Bu tek karşılaştırma
iki farklı yazımın **kararını** yan yana koyuyor: aynı başlangıç durumundan, aynı gerçek
değişiklikten yola çıkıp, yalnız bağımlılık listesine yazılıp yazılmamasına göre birbirinden
ayrışan iki sonuç.

**PA2.** Üç halkalı zincir burada üç ayrı `struct Hedef` değişkeniyle temsil ediliyor;
gerçek bir derleme sisteminde bu ilişki bir bağımlılık grafiğinde tutulur ve zincir
uzunluğu sabit değildir. Modelin ölçtüğü karar — bir halkanın eksikliğinin zincirin geri
kalanına yayılması — grafiğin boyutundan bağımsızdır.

`ara_tam` ile `son_tam`, temel'in güncellendiğini iki adımda devralarak son sürümü 2'ye
taşıyor: `ara_tam` temel'e bağlı olduğunu bildiriyor, `son_tam` da `ara_tam`'a bağlı olduğunu
bildiriyor. `ara_eksik` ise temel'e bağlı olduğunu hiç bildirmediği için kendi kaydı 1'de
donuk kalıyor; `son_eksik`'in `ara_eksik`'e olan bağlılığı **tam** yazılmış olsa bile,
devralacağı yeni bir kayıt zaten yok — `son_eksik.uretim_zamani` ile `ara_eksik.uretim_zamani`
eşit çıktığı için karşılaştırma `baslik_zamani > h->uretim_zamani` koşulunu hiç sağlamıyor.
Bu, zincirin ortasındaki tek bir eksik kaydın neden yalnız kendi hedefini değil, ona bağlı
bütün alt ağacı da etkilediğini somutlaştırıyor: son halka kendi bağlılığını doğru yazmış
olsa bile, önceki halkanın eksikliğini telafi edemiyor.

Gerçek bir kaynak ağacında zincir üç halkayla sınırlı kalmaz ve dahası doğrusal da değildir:
bir ara hedefe onlarca son hedef bağlı olabilir, ve bunların hepsi aynı eksikliği aynı anda
devralır. `ara_eksik` örneğinde tek bir `son_eksik` vardı; gerçek bir ağaçta bu, `ara_eksik`'e
bağlı her hedefin eski sürümde kalması demektir — eksikliğin etkisi tek bir hedefte değil,
o hedefe bağlı bütün hedeflerde aynı anda görünür. Bu yayılma, zincirin ortasındaki bir
eksikliği bulmayı hem daha önemli hem de bir bakıma daha kolay kılar: birden fazla hedefin
aynı anda ve aynı şekilde eski kaldığını görmek, tek bir hedefin izole bir şekilde eski
kalmasından daha belirgin bir örüntüdür ve genelde araştırmayı doğrudan ortak ataya, yani
`ara_eksik`'e yönlendirir.

Bazı derleme sistemleri bu listeyi elle yazdırmak yerine bir üreteçten türetir: kaynak
dosyaları tarayıp hangi başlığın hangi hedefe dahil edildiğini kendisi çıkarır ve
bağımlılık listesini otomatik üretir (model). Üreteç tabanlı yaklaşımın kazandırdığı şey
listenin unutulma olasılığını azaltmaktır — kural kendisi aynı kalır, yalnız listeyi kim
yazdığı değişir.

Bu iki yaklaşım arasındaki fark, elle yazılan listenin **statik**, üretilen listenin ise
**her derleme öncesi yeniden çıkarılan** olmasıdır. Elle yazılan liste bir kaynak dosya
yeni bir başlık eklediğinde güncellenmezse eksik kalır; üreteç ise her koşumda kaynağın
kendisine bakarak listeyi yeniden kurduğu için bu unutmayı yapısal olarak dışlar. Bedeli,
üreteç adımının kendisinin de doğru çalışması gerekliliğidir — üreteç bir başlığı
atlıyorsa, sonuç yine elle yazılmış eksik bir listeyle aynı sınıfa düşer, yalnız eksikliğin
kaynağı insan değil üretecin kendisi olur.

Genel hatlarıyla üreteç tabanlı yaklaşım şöyle çalışır: derleyici (ya da ona eşlik eden ayrı
bir araç) bir kaynak dosyayı işlerken hangi başlıkların gerçekten dahil edildiğini zaten
görür — çünkü önişlemci aşaması her `#include` satırını açar. Bu bilgi atılmak yerine ayrı
bir kayıt dosyasına yazılır: kaynak dosyanın adı ve o kaynağın geçişli olarak bağlı olduğu
her başlığın listesi. Bu kayıt dosyası bir sonraki derlemeden önce okunur ve derleme
sisteminin bağımlılık listesine dahil edilir. Sonuç olarak liste elle değil, kaynağın kendi
gerçek içeriğinden türetilmiş olur; bir kaynak dosya yeni bir başlık eklediğinde, bir dahaki
derlemede üreteç bu değişikliği kendiliğinden yakalar ve kayıt dosyasını günceller. Küçük bir
kaynak ağacında elle yazılan liste sorun çıkarmayabilir — birkaç dosya, birkaç başlık, hepsi
tek bakışta görülebilir. Ağaç büyüdükçe, her yeni `#include` satırının bir yerde de listeye
elle eklenmesi gerekliliği ölçeklenmiyor: eklemeyi unutmak, kod yazmaktan daha kolay hâle
geliyor, çünkü liste güncellemesi kaynak dosyayı değiştirmekle aynı anda, aynı dikkatle
yapılmıyor.

Üreteç tabanlı yaklaşımın kendi maliyeti de var: kayıt dosyası bir önceki derlemenin
çıktısıdır, dolayısıyla ilk derlemede — henüz hiçbir kayıt dosyası üretilmemişken — derleme
sistemi bağımlılık listesi olmadan başlamak zorunda kalır. Bu, birçok kural tabanlı sistemin
neden iki geçişli çalıştığını açıklar: önce var olan kayıt dosyalarını (varsa) okuyup
bağımlılık grafiğini kurar, sonra gerçek derlemeyi bu grafiğe göre sıralar. Kayıt dosyası
eksikse ya da bozuksa, sistem o hedefi güvenli tarafta kalıp yeniden üretir — eksik bilgiyi
"değişmedi" değil "bilinmiyor, o yüzden üret" olarak okur. Bu, elle yazılan listenin sessizce
eksik kalma riskine karşı üreteç tabanlı yaklaşımın yapısal avantajıdır: bir hata olduğunda
sonuç genelde gereksiz bir yeniden derleme olur, sessizce atlanan bir derleme değil.

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

Bu model yalnız **derleme sisteminin bildiği** bağımlılık ilişkisini görüyor; başlığın
içeriğini gerçekten okuyup okumadığını değil. `bagimliligi_denetle` çağrılmadığı sürece
model, başlığın hiç değişmediğini varsaydığı bir durumla aynı sonucu üretir — oysa başlık
gerçekten değişmiştir. Ölçülen şey bağımlılık **kaydının** doğruluğu; kaynağın kendisinin
doğruluğu değil. Bu ayrım önemli çünkü iki farklı hata sınıfını karıştırmamak gerekiyor:
kaydın kendisi bozuk olabilir (bu dersin konusu) ya da kayıt doğru olduğu hâlde derleyicinin
o kaydı kullanma biçimi hatalı olabilir — örneğin bir önceki modülün aşama sorununa giren bir
durum. İkisi de sonuçta "hedef beklenenden eski davranıyor" belirtisini verir, ama kökleri
tamamen ayrıdır ve düzeltmesi de ayrıdır: birincisi bağımlılık listesine eksik satırı
eklemekle, ikincisi ilgili aşamayı (önişlemci, derleyici, bağlayıcı) yeniden incelemekle
çözülür.

Bir önceki modülde bir değişikliğin hangi **aşamada** göründüğü sorulmuştu: önişlemci mi,
derleyici mi, bağlayıcı mı, yoksa çalışma zamanı mı. Buradaki soru farklı bir eksende
durur — başlığı değişen ama yeniden derlenmeyen bir birim, aşama sorusu açısından sıradan
bir önişlemci girdisidir; görünürlük sorusu açısından ise kopyadır, çünkü hedef değişikliği
hiç görmemiştir.

Bu iki eksenin birbirinden bağımsız olması, bir hatayı ayıklarken hangi soruyu sorduğunuzu
netleştirmeyi gerektirir. Bir hedefin beklenenden eski davranması iki farklı nedenden
kaynaklanabilir: ya kaynak dosya doğru aşamadan geçmedi (önişlemci bir makroyu genişletmedi,
derleyici eski bir nesne dosyasını kullandı), ya da kaynak dosya doğru aşamalardan geçti
ama derleme sistemi onu hiç yeniden derlemedi. Bu modelin ölçtüğü yalnız ikincisi — hedefin
`uretilen_surum` alanına hangi değerin ulaştığı, o değerin nasıl işlendiği değil. Bir
derleme sistemi günlüğü bu iki durumu ayırt etmenin en doğrudan yoludur: hangi hedeflerin
yeniden üretildiği, hangilerinin **hiç dokunulmadan** bırakıldığı günlükte görünür, ve
beklenmedik şekilde dokunulmamış bir hedef bu dersin ölçtüğü sınıfın somut bir belirtisidir.

Zincirleme örnek bu iki ekseni birlikte gösteriyor. `son_eksik`'in eski sürümde kalması
tek bir eksik kayıttan kaynaklanıyor — `ara_eksik`'in temel'e bağımlılığını bildirmemesi —
ama bu eksikliğin görünen yeri zincirin **sonunda**. Bir hatayı ayıklarken `son_eksik`'i
inceleyen biri önce kendi bağımlılık listesine bakar, orayı doğru bulur (`son_eksik`,
`ara_eksik`'e bağlı olduğunu doğru bildirmiş) ve hatayı orada aramaz; gerçek eksiklik bir
önceki halkada durur. Bu, geçişli bir zincirde hatayı ayıklamanın neden tek bir hedefe
bakmakla bitmediğini gösteriyor — sorun, zincirin görünen ucundan değil, zincirin kendisi
boyunca geriye doğru izlenerek bulunur. Büyük bir kaynak ağacında bu iz sürme elle
yapılamayacak kadar çok halka içerebilir; bu yüzden üreteç tabanlı yaklaşımın kayıt
dosyaları aynı zamanda bir tanı aracı gibi de kullanılır — hangi hedefin hangi başlıklara
gerçekten bağlı olduğunu, insan hafızasına değil dosyaya bakarak doğrulamak için.

#### Özet

- Kural tabanlı bir derleme sistemi, hedefin bağımlı olduğu her girdinin zaman damgasını
  hedefin kendi üretim zamanıyla karşılaştırır.
- Bağımlılık listesi eksiksiz olmadığında, listede yer almayan bir değişiklik hiç
  sorulmaz ve hedef eskisini korur.
- `yazilmamis` hedefinin `uretilen_surum` alanı, başlığın gerçek sürümünden bağımsız,
  yazan tarafın eski bıraktığı değerin bir kopyasıdır — bu dersin ölçtüğü sınıf **kopya**.
- Üreteç tabanlı yaklaşım bağımlılık listesini otomatik türeterek unutulma olasılığını
  azaltır; kuralın kendisini değiştirmez.
- Görünürlük sorusu, bir önceki modülün aşama sorusundan bağımsız bir eksendir.
- Bağımlılık zinciri geçişlidir: zincirin ortasındaki eksik bir kayıt, ona bağlı bütün
  alt ağacı da eski durumda bırakır — hata zincirin görünen ucunda değil, eksik kaydın
  bulunduğu halkada aranmalıdır; üreteç tabanlı sistemler eksik bilgiyi "değişmedi" değil
  "bilinmiyor" olarak okuyup güvenli tarafta kalıp yeniden ürettiği için hata payı gereksiz
  derlemeye kayar, sessiz atlamaya değil.

Pratikte bir bağımlılığın listeye yazılmadan kalmasının birkaç tipik nedeni var. Bir kaynak
dosya yeniden düzenlenip yeni bir başlığa bölündüğünde, eski listenin güncellenmesi ayrı bir
adımdır ve atlanabilir. Bir dosya bir dizinden başka bir dizine taşındığında, listedeki yol
eski konumu göstermeye devam edebilir; kural bu durumda ya hiç eşleşmez ya da yanlış dosyayı
izler. Kopyala-yapıştırla oluşturulan yeni bir hedef, kaynağındaki bağımlılık listesini de
kopyalar ama kendi gerçek bağımlılıklarını yansıtmayabilir — yeni hedefin kendine özgü
başlıkları listeye hiç girmemiş olur. Bu üç durum da aynı sonuca varıyor: liste, kaynağın
şu anki gerçek durumunu değil, bir önceki düzenlemenin anlık görüntüsünü taşıyor. Üreteç
tabanlı yaklaşımın kapattığı boşluk tam olarak bu — listenin kaynaktan **ayrı bir yapı**
olarak elle bakımının hiç gerekmemesi.

#### Sonraki Adım

Bir hedef doğru derlense bile üretilen kod tek başına bir programa dönüşmez — çoğu zaman
başka birimlerle **bağlanır**. Bu bağlanma da kendi zaman damgası sorusunu taşır: bir
kütüphane güncellendiğinde, ona bağlı program bu güncellemeyi ne zaman görür — o an mı,
yoksa bir sonraki yeniden derlemede mi, hatta hiç mi? Bir sonraki ders, bir kütüphaneye
bağlanmanın iki farklı biçiminin, o kütüphane güncellendiğinde programın hangi sürümü
göreceğini nasıl belirlediğini ölçüyor; bu derste kurulan "listede yazılı mı, değil mi"
sorusunun bağlanma aşamasındaki karşılığı budur.

### Kütüphane Üretimi

Source: https://academia.sh/tr/kurslar/c-sistem-programlama/kutuphane-uretimi

Bir kütüphanenin programa gömülerek mi yoksa yüklemede okunarak mı bağlandığının, kütüphane sürümü değiştiğinde programın hangi değeri gördüğünü nasıl belirlediğini ölçer.

Bir hedef doğru derlenip nesne haline geldikten sonra tek başına çalışmaz — çoğu program
başka birimlerin ürettiği işlevlere ve verilere bağlanır. Bu bağlanma iki farklı biçimde
olabilir: kütüphanenin o anki değeri programın kendi gövdesine **kopyalanır**, ya da program
kütüphaneye bir **işaretçiyle** erişir ve her seferinde güncel değeri okur. Kütüphane
sonradan güncellendiğinde bu iki program birbirinden farklı bir şey görür.

Aynı ayrım, tek bir çeviri biriminin dışına açılmayan bir değişkenle dışa açılan bir
değişken arasında da yaşanır. `static` bir dosya kapsamı değişkeni her çeviri birimine
kendi kopyasını verir; `extern` bildirimiyle paylaşılan bir değişkenin ise tek bir tanımı
vardır ve onu kullanan her birim aynı nesneyi görür.

Bu ders, bir önceki dersteki **kopya** sınıfını farklı bir katmanda tekrar karşımıza
çıkarıyor. Orada kopya, bağımlılık listesine yazılmamış bir kaydın taşıdığı eski değerdi;
burada kopya, bağlanma anında alınıp bir daha güncellenmeyen bir kütüphane sürümüdür. İkisi
de aynı belirtiyi verir — beklenenden eski bir değer — ama biri derleme sisteminin karar
katmanında, diğeri bağlanma katmanında oluşur. Bu iki katmanı ayırt edebilmek, bir hatayı
ararken nereye bakılacağını daraltmanın ilk adımıdır.

#### Gömülü Sürüm ve İşaretçi Tabanlı Erişim

Aşağıdaki model bir kütüphanenin sürümünü ve ona iki farklı yoldan bağlanan iki yapıyı
tanımlıyor: biri sürümü bağlama anında kendi alanına kopyalıyor, diğeri kütüphaneye giden
işaretçiyi saklıyor.

```c
// baglama.h — gomulu ve isaretci tabanli baglamanin surum gorunurlugu
#ifndef BAGLAMA_H
#define BAGLAMA_H

struct Kutuphane {
    int surum;
};

struct GomuluBagli {
    int gomulu_surum;   /* baglama aninda kopyalanan deger, sonrasinda bagimsiz */
};

struct IsaretciBagli {
    const struct Kutuphane *hedef;   /* deger degil, kutuphaneye giden yol saklanir */
};

void gomulu_bagla(struct GomuluBagli *g, const struct Kutuphane *k);
int gomulu_surum(const struct GomuluBagli *g);

void isaretci_bagla(struct IsaretciBagli *b, const struct Kutuphane *k);
int isaretci_surum(const struct IsaretciBagli *b);

#endif
```

```c
// baglama.c — gomulu baglama kopyalar, isaretci baglama her okumada yeniden okur
#include "baglama.h"

void gomulu_bagla(struct GomuluBagli *g, const struct Kutuphane *k) {
    g->gomulu_surum = k->surum;
}

int gomulu_surum(const struct GomuluBagli *g) {
    return g->gomulu_surum;
}

void isaretci_bagla(struct IsaretciBagli *b, const struct Kutuphane *k) {
    b->hedef = k;
}

int isaretci_surum(const struct IsaretciBagli *b) {
    return b->hedef->surum;
}
```

**PA3.** `gomulu_bagla` burada gerçek bir programın derleme anında yaptığı gömmeyi
temsil ediyor: kütüphanenin o anki değeri, program çalışmaya başlamadan önce sabitlenmiş
gibi davranıyor. Gerçek bir derleme sisteminde bu adım derleme sırasında olur; burada
zamanlamayı görünür kılmak için çalışma zamanında bir kopyalama işlemiyle modelleniyor.

Bu iki yapı, gerçek bir derleme ortamındaki iki kütüphane bağlama biçiminin karşılığıdır.
`GomuluBagli`, **statik bağlı** bir kütüphaneyi temsil ediyor: kütüphanenin derlenmiş
kodu, programın kendi yürütülebilir dosyasının içine doğrudan kopyalanır ve program bir
daha kütüphaneye bakmaz. Kütüphane sonradan güncellenip ayrıca derlense bile, zaten
statik bağlı programın gövdesinde donmuş kalan kopya değişmez — programın kendisi yeniden
bağlanmadıkça, güncel sürüm ona hiç ulaşmaz. `IsaretciBagli` ise **paylaşılan** (dinamik)
bağlı bir kütüphaneyi temsil ediyor: yürütülebilir dosya kütüphanenin kendi kodunu taşımaz,
yalnız ona giden bir yolu tutar ve her erişimde kütüphaneye gider. Kütüphane güncellenip
yeniden üretildiğinde, programın kendisi hiç yeniden derlenmeden bir sonraki çalıştırmada
güncel sürümü görür — çünkü okuma programın gövdesine gömülü bir kopyadan değil, kütüphanenin
kendisinden yapılır.

**PA4.** Statik ve paylaşılan bağlamanın gerçek ortamdaki farkı yükleme zamanlamasında ve
disk üzerindeki ayrı bir kütüphane dosyasının varlığındadır; bu model o zamanlama farkını
tek bir çalışma zamanı akışı içinde, `gomulu_bagla` çağrısının kütüphaneyi güncellemeden
önce yapılmasıyla temsil ediyor. Ölçülen sonuç — statik bağlının eski kaldığı, paylaşılanın
günceli gördüğü — zamanlamanın kendisinden değil, kopyalama ile referans tutmanın farkından
doğuyor, dolayısıyla model gerçek yükleme mekanizmasını atlasa da sonucu değiştirmiyor.

#### static ve extern Kapsam

Bir kütüphane birden çok çeviri birimine bölündüğünde, her birimin kendi dosyasına özel
sayaçları ile bütün birimlerin paylaştığı tek bir sayaç arasındaki fark aynı çerçevede
ölçülebilir.

Bu karşıtlık **başlıkta** kurulur, kaynak dosyada değil: bir değişkenin `paylas.h` gibi
birden çok kaynak dosyanın dahil ettiği bir başlıkta `extern` olarak bildirilmesi, o
değişkenin tek bir tanımı olacağının ve onu dahil eden her birimin **aynı nesneyi**
göreceğinin sözünü verir. Aynı başlığa `static` bir bildirim yazmak bu sözü veremezdi —
`static`, kapsamını doğrudan bulunduğu çeviri birimiyle sınırlar; bir başlığa yazılıp her
kaynak dosyaya dahil edilseydi, her dahil eden birim kendi **ayrı** kopyasını üretirdi,
paylaşılan tek bir nesne değil. Bu yüzden paylaşım isteniyorsa bildirim başlıkta `extern`
olmalı ve tanım tam olarak bir kaynak dosyada bulunmalıdır; birime özel tutmak isteniyorsa
bildirim kaynak dosyanın kendisinde `static` olarak kalmalı ve hiç başlığa taşınmamalıdır.

Bu ayrımı yanlış kurmanın iki farklı sonucu var. Bir değişken paylaşılması gerekirken
yanlışlıkla `static` bırakılırsa — örneğin başlığa hiç `extern` bildirimi eklenmeden her
kaynak dosyanın kendi kopyasını tanımlaması istenirse — birimler birbirinden habersiz ayrı
sayaçlar tutar ve `ortak_sayac` gibi paylaşılan bir toplam hiç oluşmaz; her birim kendi
gördüğü kısmi sayıyı doğru sanır. Tam tersi yönde bir hata da mümkündür: aynı adlı bir
değişken birden fazla kaynak dosyada `extern` olmadan, doğrudan tanımlanırsa — yani her
kaynak dosya kendi başına o ismi paylaşılan bir nesne gibi tanımlamaya çalışırsa —
bağlayıcı aynı adı birden fazla yerde tanımlanmış bulur ve bu genelde bir bağlama hatasıyla
sonuçlanır. Başlıktaki `extern` bildirimi bu ikinci hatayı da önler, çünkü başlığı dahil
eden her birime yalnız bir **bildirim** verir, bir **tanım** değil; tanım tam olarak bir
kaynak dosyada kalır.

```c
// paylas.h — ortak sayaci disa acan bildirim
#ifndef PAYLAS_H
#define PAYLAS_H

extern int ortak_sayac;    /* extern: tek tanim, her birim ayni nesneyi paylasir */

void durum_artir(void);
int durum_sayaci(void);

#endif
```

```c
// durum.c — statik sayac bu birime ozel, extern sayac paylasilan
#include "paylas.h"

static int birim_sayaci = 0;   /* static: bu ceviri birimine ozel kopya */
int ortak_sayac = 0;            /* extern bildiriminin tek tanimi burada */

void durum_artir(void) {
    birim_sayaci++;
    ortak_sayac++;
}

int durum_sayaci(void) {
    return birim_sayaci;
}
```

`main` içeren birim de kendi `static` sayacını tutuyor; adı `durum.c` içindekiyle aynı
olsa da `static` kapsam bunu ayrı bir nesne yapıyor, derlemeyi bozmuyor.

```c
// olcum.c — gomulu/isaretci baglama ve static/extern kapsam tek kosumda
#include <stdio.h>
#include "baglama.h"
#include "paylas.h"

static int birim_sayaci = 0;   /* bu birime ozel; durum.c'deki ayni adli degiskenden ayri */

int main(void) {
    struct Kutuphane kutuphane = { 1 };
    struct GomuluBagli gomulu;
    struct IsaretciBagli isaretcili;

    gomulu_bagla(&gomulu, &kutuphane);
    isaretci_bagla(&isaretcili, &kutuphane);

    kutuphane.surum = 2;   /* kutuphane baglamadan sonra 2. surume cikti */

    printf("gomulu bagli surum: %d\n", gomulu_surum(&gomulu));
    printf("isaretci bagli surum: %d\n", isaretci_surum(&isaretcili));

    durum_artir();
    durum_artir();
    birim_sayaci++;
    ortak_sayac++;

    printf("durum.c birim sayaci: %d\n", durum_sayaci());
    printf("main birim sayaci: %d\n", birim_sayaci);
    printf("ortak sayac: %d\n", ortak_sayac);
    return 0;
}
```

```text
gomulu bagli surum: 1
isaretci bagli surum: 2
durum.c birim sayaci: 2
main birim sayaci: 1
ortak sayac: 3
```

`gomulu` yapısı, bağlama anında okuduğu 1 değerinde kaldı — kütüphane sonradan 2. sürüme
çıktığında bundan habersizdir, çünkü elinde tuttuğu şey bir **kopya**. `isaretcili` yapısı
ise aynı kütüphane nesnesine giden yolu sakladığı için güncel değeri okudu; bu, gömülü
bağlamanın karşıtı olan **ortak** görünürlüğün bir modelidir — bu dersin kendisi
ölçmüyor, yalnız karşılaştırma için burada duruyor.

Bu iki sonuç, statik ve paylaşılan bağlamanın kütüphane güncellemesine verdiği yanıtı
birebir yansıtıyor. `gomulu` statik bağlı bir programın davranışını modelliyor: kütüphane
2. sürüme çıktı, ama program zaten kendi gövdesine 1. sürümün kopyasını gömmüştü ve bu
kopya bir daha değişmedi — kütüphanenin yeni sürümünü görmesinin tek yolu, programın
kütüphaneye **yeniden bağlanarak** kendisinin de yeniden üretilmesidir. `isaretcili` ise
paylaşılan bağlı bir programın davranışını modelliyor: program kendisi hiç yeniden
derlenmedi, ama kütüphaneye giden yolu her okuduğunda kütüphanenin **o anki** durumunu
gördüğü için güncellemeyi otomatik olarak yakaladı. Bu fark, iki bağlama biçimi arasında
seçim yaparken göz önünde tutulması gereken temel değiş tokuşu özetliyor: statik bağlama
öngörülebilir ve kütüphanenin dışarıdan değişmesine karşı bağışıktır, ama güncellemeyi
almak için yeniden bağlanma ister; paylaşılan bağlama güncellemeyi otomatik alır, ama bu
otomatikliğin bedeli, kütüphanenin ne zaman değiştiğinin programın kendi kontrolü dışında
olmasıdır.

`durum.c` birimindeki `static` sayaç yalnız kendi çağrılarını saydı ve 2'de kaldı; `main`
birimindeki aynı adlı `static` sayaç ise ondan tamamen bağımsız, kendi 1 değerini taşıyor.
`ortak_sayac` ise her iki birimin de arttırdığı tek nesne olduğu için ikisinin toplamı olan
3'e ulaştı.

Aynı `static`/`extern` ayrımı fonksiyonlar için de geçerlidir, yalnız bu derste değişken
üzerinden gösteriliyor çünkü kopyalanan değerin görünürlüğü değişkende daha doğrudan
izlenebilir. Bir fonksiyon `static` bildirilirse, o fonksiyon yalnız kendi çeviri biriminden
çağrılabilir; aynı adı taşıyan başka bir `static` fonksiyon başka bir birimde tanımlanabilir
ve ikisi birbirine karışmaz, çünkü bağlayıcı ikisini de dışa açık bir sembol olarak görmez.
Bu, büyük bir kod tabanında yardımcı fonksiyon adlarının çakışmasını önlemenin ucuz bir
yoludur: bir birime özel kalması gereken her yardımcı fonksiyon `static` bırakılır, yalnız
gerçekten paylaşılması gereken fonksiyonlar başlığa `extern` bildirimiyle (ya da bildirimsiz,
çünkü fonksiyonlar C'de varsayılan olarak `extern`'dür) taşınır. Bu seçim erken yapılmazsa
sonradan geri almak ucuz değildir: bir fonksiyon önce paylaşılan olarak yayılır, başka
birimler onu çağırmaya başlar, sonra birim içine kapatılmak istenirse artık onu çağıran her
yerin de değişmesi gerekir — bu yüzden görünürlük kararı, kütüphanenin arayüzünü ilk kez
çizerken, sonradan değil baştan verilir.

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

Bu model kütüphanenin gerçekte nasıl yüklendiğini değil, yalnız iki bağlama biçiminin
**hangi değere eriştiğini** ölçüyor. Gerçek bir ortamda gömülü bağlama derleme sırasında,
işaretçi tabanlı bağlama ise programın başlatılması sırasında kurulur; burada ikisi de tek
bir çalışma zamanı akışı içinde, zamanlamaları görünür kılınarak modelleniyor. Kütüphanenin
arayüzü — yani `struct Kutuphane` içindeki alanların sırası ve türleri — iki taraf arasında
değişirse, işaretçi tabanlı bağlamanın eski değil güncel değeri okuması artık bir güvence
değildir; bu, tanımsız davranışa açılan bir sınırdır ve burada koşturulmaz.

Statik ve paylaşılan bağlamanın gerçek dünyadaki sonucu genelde bir güvenlik ya da hata
düzeltmesinin dağıtımında ortaya çıkar. Kütüphanenin bir sürümünde bir hata düzeltilip
kütüphane yeniden üretildiğinde, paylaşılan bağlı bir program bu düzeltmeyi kendisi hiç
dokunulmadan, bir sonraki çalıştırmasında devralır — düzeltme kütüphaneyle birlikte
dağıtılır. Statik bağlı bir program ise aynı düzeltmeyi almaz; kendi gövdesine gömdüğü eski
kod çalışmaya devam eder, ve düzeltmenin ona ulaşması için programın kendisinin kütüphaneye
yeniden bağlanıp yeniden dağıtılması gerekir. Bu, iki bağlama biçimi arasındaki seçimin
yalnız bir performans ya da dosya boyutu meselesi olmadığını, aynı zamanda bir bakım ve
dağıtım sorumluluğu meselesi olduğunu gösteriyor: paylaşılan bağlama güncellemeyi merkezi
bir noktadan (kütüphanenin kendisinden) yayarken, statik bağlama her programın kendi
kopyasının ayrı ayrı güncellenmesini gerektiriyor.

Bir önceki dersin sorusu bağımlılığın **yazılıp yazılmadığıydı**; bu ders bağımlılık
doğru yazılmış olsa bile, ona **nasıl** bağlanıldığının görünürlüğü değiştirdiğini
gösteriyor — iki soru birbirini tamamlıyor, aynı aşamada durmuyor.

Bu iki soru aynı hataya iki farklı açıklama getirebilir. Bir program beklenmedik şekilde
eski bir değer taşıyorsa, önce bağımlılığın derleme sisteminin listesinde yazılı olup
olmadığına bakılır; liste doğruysa ama değer yine de eski kalıyorsa, ikinci soru devreye
girer — bağlanma biçimi gömülü mü, işaretçi tabanlı mı? Gömülü bağlanan bir değer, listeye
doğru yazılmış bir bağımlılığın bile eski bir kopyasını taşıyabilir, çünkü kopyalama zaten
bağlanma anında, kütüphane değişmeden önce gerçekleşmiştir. Bu iki neden birbirine benzer
sonuç üretir — her ikisinde de program eski bir değer görür — ama kaynakları farklıdır ve
farklı düzeltmeler gerektirir: biri bağımlılık listesinin tamamlanmasını, diğeri bağlanma
biçiminin işaretçi tabanlıya çevrilmesini ister.

Bu iki neden arasında ayrım yapmanın pratik bir yolu, kütüphaneyi değiştirip programı
**yeniden derlemeden** yeniden çalıştırmaktır. Bağlanma gömülüyse, kütüphanedeki değişiklik
hiçbir fark yaratmaz — program hâlâ eski, gömülü değeri taşır, çünkü kopyalama derleme
anında bitmiştir ve kütüphanenin sonradan değişmesi programın belleğindeki kopyaya ulaşamaz.
Bağlanma işaretçi tabanlıysa, aynı adım yeni değeri hemen ortaya çıkarır, çünkü program her
seferinde kütüphaneye tekrar erişir. Bu tek adım, listeye yazılmama sorununu tekrarlamaktan
farklıdır: listeye yazılmama sorununda kütüphane hiç doğru okunmaz, bağlanma biçimi
sorununda kütüphane doğru okunur ama okuma anı yanlış zamana denk gelir.

Statik ve paylaşılan bağlamanın üçüncü bir sonucu, kütüphanenin arayüzü değiştiğinde ortaya
çıkar. `struct Kutuphane` gibi bir yapının alan sırası ya da türü değişirse, statik bağlı bir
program bu değişiklikten habersiz kalabilir çünkü kendi gövdesine eski arayüzün bir kopyasını
gömmüştür; yeniden derlenmediği sürece hatayı fark etmez, ama yeniden derlendiğinde derleme
anında hata verir ve sorun görünür hâle gelir. Paylaşılan bağlı bir program ise arayüz
değişikliğini derleme anında değil çalışma anında karşılayabilir — kütüphane yeni arayüzle
yeniden üretilip programın gövdesi değişmeden bırakılırsa, program eski arayüzü beklerken
kütüphane yeni arayüzü sunar ve bu uyumsuzluk derleme hatası değil çalışma zamanı hatası
olarak ortaya çıkar. Bu farkın adı **arayüz uyumluluğudur**: statik bağlamada uyumsuzluk
erken ve gürültülü biçimde durur, paylaşılan bağlamada geç ve sessiz biçimde sürebilir.

#### Özet

- Gömülü bağlama, kütüphanenin değerini bağlama anında kopyalar; kütüphane sonradan
  değişse bile program eski değeri taşımaya devam eder — bu dersin ölçtüğü sınıf **kopya**.
- İşaretçi tabanlı bağlama kütüphaneye giden yolu saklar ve her okumada güncel değeri
  görür; bu, gömülü bağlamanın karşıtı olan ortak görünürlüğün bir modelidir.
- `static`/`extern` karşıtlığı başlıkta kurulur: paylaşım isteniyorsa bildirim başlıkta
  `extern` olmalı ve tanım tek bir kaynak dosyada olmalı; birime özel tutmak isteniyorsa
  bildirim hiç başlığa taşınmadan kaynak dosyada `static` kalmalıdır — `static` her dahil
  eden birime ayrı bir kopya verir, `extern` tek bir paylaşılan nesneye bağlar.
- Kütüphanenin arayüzü iki taraf arasında uyuşmazsa işaretçi tabanlı bağlamanın güncel
  değeri güvenli okuması garanti değildir.
- Statik bağlama bir güncellemeyi almak için programın yeniden bağlanmasını ister;
  paylaşılan bağlama güncellemeyi kütüphanenin kendisinden merkezi olarak dağıtır.

#### Sonraki Adım

Bir kütüphanenin doğru bağlandığını bilmek, onu kullanan kodun doğru çalıştığını
göstermez. Bir sonraki ders, bir birimi geri kalan programdan **yalıtarak** sınamanın,
bir değişikliğin başka bir teste sızıp sızmadığını nasıl ölçtüğünü ele alıyor.

### Test Çerçeveleri

Source: https://academia.sh/tr/kurslar/c-sistem-programlama/test-cerceveleri

Çatallanmış bir çocukta koşan bir testin değişikliğinin neden sonraki teste sızmadığını, ve bu yalıtımın dosya sistemi durumunu neden kapsamadığını ölçer.

Bir kütüphaneye doğru bağlanmak, onu kullanan kodun doğru çalıştığını göstermez. Bunu
sınamanın kendisi de bir görünürlük sorunu taşır: bir test başka bir testin bıraktığı
durumu görüyorsa, hangi testin gerçekten başarısız olduğunu artık söyleyemezsiniz. Küçük
bir test çerçevesi bu sorunu, her testi kendi kopyasında çalıştırarak çözer.

#### Test Tablosu ve Yalıtılmış Koşum

Bir test çerçevesinin çekirdeği bir işlev işaretçisi tablosu, tek bir onaylama makrosu ve
bir geçti/kaldı sayımıdır. Onaylama başarısız olduğunda test işlevi 0 döndürür; onaylanacak
bir şey kalmadan sona ererse 1 döndürür.

```c
// cerceve.h — test tablosu, onaylama makrosu ve paylasilan durumun bildirimi
#ifndef CERCEVE_H
#define CERCEVE_H

typedef int (*TestIslevi)(void);

struct TestGirdisi {
    const char *ad;
    TestIslevi calistir;
};

#define ONAYLA(kosul) do { if (!(kosul)) return 0; } while (0)

extern int ortak_durum;          /* testler arasi sizintiyi gozlemlemek icin */
extern int sahte_cagri_sayaci;   /* sahte bagimliligin kac kez cagrildigi */
extern int duzenek_kurulum_sayaci;  /* duzenegin test oncesi kac kez kuruldugu */
extern int duzenek_temizlik_sayaci; /* duzenegin test sonrasi kac kez sokuldugu */

int test_durumu_ayarla(void);
int test_durumu_sifir_mi(void);
int test_duzenek_degerini_gor(void);
int test_dosyaya_yaz(void);
int test_dosyadan_oku(void);
int sahte_bagimlilik(int girdi);
int guduk_bagimlilik(int girdi);
int gercek_isi_yap(int girdi, int (*bagimlilik)(int));
int duzenek_kur(void);
int duzenek_sok(void);

#endif
```

```c
// cerceve.c — test govdeleri, sahte bagimlilik ve dosya tabanli iki test
#include <stdio.h>
#include "cerceve.h"

int ortak_durum = 0;
int sahte_cagri_sayaci = 0;

int test_durumu_ayarla(void) {
    ortak_durum = 9;
    return 1;
}

int test_durumu_sifir_mi(void) {
    ONAYLA(ortak_durum == 0);
    return 1;
}

int test_duzenek_degerini_gor(void) {
    ONAYLA(ortak_durum == 5);   /* duzenek_kur burayi 5'e getirmis olmali */
    return 1;
}

int test_dosyaya_yaz(void) {
    FILE *f = fopen("iz.dat", "w");
    if (!f) return 0;
    fputc('1', f);
    fclose(f);
    return 1;
}

int test_dosyadan_oku(void) {
    FILE *f = fopen("iz.dat", "r");
    if (!f) return 0;
    int deger = fgetc(f);
    fclose(f);
    return deger == '1';
}

int sahte_bagimlilik(int girdi) {
    sahte_cagri_sayaci++;
    return girdi * 2;
}

int duzenek_kurulum_sayaci = 0;
int duzenek_temizlik_sayaci = 0;

int guduk_bagimlilik(int girdi) {
    (void)girdi;             /* gudugun cevabi sabittir, girdiyi hic okumaz */
    return 42;                /* onceden kararlastirilmis, degismeyen cevap */
}

int gercek_isi_yap(int girdi, int (*bagimlilik)(int)) {
    return bagimlilik(girdi) + 1;
}

int duzenek_kur(void) {
    duzenek_kurulum_sayaci++;
    ortak_durum = 5;    /* duzenegin kurdugu bilinen baslangic durumu */
    return 1;
}

int duzenek_sok(void) {
    duzenek_temizlik_sayaci++;
    ortak_durum = 0;    /* bir sonraki test icin durumu temiz birak */
    return 1;
}
```

**PA5.** Test tablosu yalnız iki girdiyle gösteriliyor; mekanizma herhangi bir sayıda
girdiye genelliyor, çünkü koşum sırası tablo üzerinde bir döngüden ibaret.

Her testi kendi çocuğunda koşturan yardımcı, önceki modülde kurulan çatallama sözleşmesini
kullanıyor: tampon boşaltılıyor, çocuk testi çalıştırıp çıkış durumuyla sonucu bildiriyor,
ebeveyn yalnız o durumu okuyor.

**PA6.** Düzenek burada tek bir çift işlevle — bir kurulum, bir temizlik — gösteriliyor;
gerçek bir çerçevede düzenek testler arasında farklılaşabilir, ama çift olma zorunluluğu
(her kuruluma karşılık bir temizlik) değişmiyor.

Testin kendisinden önce ve sonra çalışması gereken ortak bir hazırlık varsa, bu hazırlığı
her testin gövdesine tek tek yazmak yerine ayrı bir **düzenek** (kurulum/temizlik çifti)
olarak çıkarmak, aynı hazırlığın tekrar tekrar yazılmasını önler ve daha önemlisi, temizliğin
testin sonucundan bağımsız olarak her zaman çalışmasını garanti eder. Bir düzeneğin sözleşmesi
şudur: kurulum testten önce tam olarak bir kez çalışır, temizlik testten sonra tam olarak bir
kez çalışır — test geçse de kalsa da, hatta test bir onaylamada erken dönse bile. Aşağıdaki
`duzenek_kur`/`duzenek_sok` çifti bu sözleşmeyi modelliyor: `duzenek_kur` `ortak_durum`'u
bilinen bir başlangıç değerine (5) getiriyor ve kurulum sayacını artırıyor; `duzenek_sok`
durumu sıfırlıyor ve temizlik sayacını artırıyor. Bu iki sayaç, düzeneğin gerçekten her test
için bir kez kurulup bir kez söküldüğünü doğrulamanın yolu.

Güdük, sahte nesneden farklı bir amaca hizmet eder ve bu ders ikisini ayrı tutuyor. Bir
**güdük** (`guduk_bagimlilik`), kendisine hangi girdi verilirse verilsin sabit, önceden
kararlaştırılmış bir cevap döndürür ve çağrıldığını hiçbir şekilde kaydetmez — amacı yalnız
gerçek bağımlılığın yerini doldurup testin geri kalanının çalışabilmesini sağlamaktır. Bir
**sahte nesne** (`sahte_bagimlilik`) ise buna ek olarak kendi çağrılma biçimini gözlenebilir
kılar; bu derste bunu bir çağrı sayacıyla yapıyor. Ayrım şuna indirgenir: güdük testin
**girdisini** hazırlar, sahte nesne testin **doğrulamasının** bir parçası olur. Bir test
yalnız `gercek_isi_yap`'ın doğru bir sayı ürettiğini görmek istiyorsa güdük yeterlidir ve
daha basittir; test ayrıca bağımlılığın kaç kez çağrıldığını da doğrulamak istiyorsa sahte
nesne gerekir. İkisini karıştırıp her yerde sahte nesne kullanmak, çağrı sayısı hiç
sorulmayan testlere gereksiz bir gözlem yükü bindirir; her yerde güdük kullanmak ise çağrı
davranışını doğrulaması gereken testleri kör bırakır.

```c
// olcum.c — yalitilmis kosum, yalitilmamis sizinti ve dosya sistemi siniri tek kosumda
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "cerceve.h"

static int yalitilmis_calistir(TestIslevi islev) {
    fflush(stdout);   /* aksi halde alt surec, ana surecin bosaltilmamis tamponunu da tasir */
    pid_t pid = fork();
    if (pid == 0) {
        _exit(islev() ? 0 : 1);
    }
    int durum = 0;
    waitpid(pid, &durum, 0);
    return WIFEXITED(durum) && WEXITSTATUS(durum) == 0;
}

/* duzenekli_calistir: kurulum ve temizlik ebeveynde, testin kendisi yalitilmis cocukta */
static int duzenekli_calistir(TestIslevi islev) {
    duzenek_kur();
    int sonuc = yalitilmis_calistir(islev);
    duzenek_sok();
    return sonuc;
}

int main(void) {
    printf("yalitilmis durum ayarla: %s\n",
           yalitilmis_calistir(test_durumu_ayarla) ? "gecti" : "kaldi");
    printf("yalitilmis durum sifir mi: %s\n",
           yalitilmis_calistir(test_durumu_sifir_mi) ? "gecti" : "kaldi");

    /* ayni iki test bu kez yalitilmadan, dogrudan ayni surecte cagriliyor */
    test_durumu_ayarla();
    printf("yalitilmamis durum sifir mi: %s\n",
           test_durumu_sifir_mi() ? "gecti" : "kaldi");

    int sonuc = gercek_isi_yap(3, sahte_bagimlilik);
    printf("sahte bagimlilik cagri sayisi: %d\n", sahte_cagri_sayaci);
    printf("gercek_isi_yap sonucu: %d\n", sonuc);

    int guduk_sonuc = gercek_isi_yap(3, guduk_bagimlilik);
    printf("guduk ile gercek_isi_yap sonucu: %d\n", guduk_sonuc);

    printf("duzenekli test degeri gor: %s\n",
           duzenekli_calistir(test_duzenek_degerini_gor) ? "gecti" : "kaldi");
    printf("duzenek kurulum sayaci: %d\n", duzenek_kurulum_sayaci);
    printf("duzenek temizlik sayaci: %d\n", duzenek_temizlik_sayaci);
    printf("duzenekten sonra ana surecte ortak_durum: %d\n", ortak_durum);

    printf("yalitilmis dosyaya yaz: %s\n",
           yalitilmis_calistir(test_dosyaya_yaz) ? "gecti" : "kaldi");
    printf("yalitilmis dosyadan oku: %s\n",
           yalitilmis_calistir(test_dosyadan_oku) ? "gecti" : "kaldi");
    return 0;
}
```

```text
yalitilmis durum ayarla: gecti
yalitilmis durum sifir mi: gecti
yalitilmamis durum sifir mi: kaldi
sahte bagimlilik cagri sayisi: 1
gercek_isi_yap sonucu: 7
guduk ile gercek_isi_yap sonucu: 43
duzenekli test degeri gor: gecti
duzenek kurulum sayaci: 1
duzenek temizlik sayaci: 1
duzenekten sonra ana surecte ortak_durum: 0
yalitilmis dosyaya yaz: gecti
yalitilmis dosyadan oku: gecti
```

İlk iki satır aynı `ortak_durum` değişkenini kullanan iki testin, birbirinden ayrı çocuklarda
koştuğunu gösteriyor: `test_durumu_ayarla` kendi çocuğunda `ortak_durum`'u 9 yapıyor, ama bu
çocuk çıktığında o değişiklik onunla birlikte kayboluyor. `test_durumu_sifir_mi` kendi
çocuğunda çalıştığında, ebeveynin hâlâ 0 olan `ortak_durum` değerinin bir kopyasını devralıyor
ve onaylama geçiyor. Üçüncü satır aynı iki testi yalıtımsız çalıştırıyor: ikisi de tek
süreçte, gerçek `ortak_durum` üzerinde çalışıyor, birincinin bıraktığı 9 ikinciyi doğrudan
etkiliyor ve onaylama kalıyor — bu, süreç yalıtımının önlediği sızıntının kendisi.

Bu sızıntının tehlikeli yanı, sonucun test dosyasındaki **sıraya** bağlı olmasıdır. Aynı iki
test, `test_durumu_sifir_mi` önce çalışacak şekilde yazılsaydı, sızıntı hiç görünmez ve iki
test de yalıtımsız haliyle bile geçerdi — çünkü `ortak_durum` henüz 9'a çekilmemiş olurdu.
Bu, yalıtımsız bir test paketinin en aldatıcı özelliğidir: paket bir sırada tamamen yeşil
görünür, testler yeniden düzenlendiğinde ya da yeni bir test araya girdiğinde aynı paket
kırmızıya döner, oysa hiçbir testin kendi mantığı değişmemiştir. Hatanın kaynağı testlerin
hiçbirinde değil, aralarındaki gizli bağımlılıktadır — ve bu bağımlılık kod incelemesinde
görünmez kalır, çünkü iki test birbirini hiç çağırmaz, yalnız aynı değişkeni paylaşır.
Süreç yalıtımının asıl kazancı burada ortaya çıkıyor: her test kendi çocuğunda çalıştığı
için, testler hangi sırayla yazılırsa yazılsın sonuç aynı kalır — sıra, testin doğruluğunu
etkileyen bir değişken olmaktan çıkar.

`sahte_bagimlilik` gerçek bağımlılığın yerine geçen bir işlev işaretçisi; `gercek_isi_yap`
onu kendi mantığını değiştirmeden çağırıyor ve çağrı sayısı `sahte_cagri_sayaci` üzerinden
ölçülüyor. Bu sayaç da tıpkı `ortak_durum` gibi bir süreç belleğinde yaşıyor — bir test bunu
çatallanmış bir çocukta okusaydı, o çocuğun gördüğü sayım da kendi kopyası olurdu ve
ebeveyne geri dönmezdi; ebeveyne dönen tek şey testin geçip geçmediğidir.

Sahte nesnenin burada değiştirmediği şey `gercek_isi_yap`'ın kendi mantığıdır: işlev hâlâ
kendisine verilen işaretçiyi çağırıyor, yalnız o işaretçinin gösterdiği gövde gerçek
bağımlılık yerine sayaç tutan bir gövdeyle değiştirildi. Bu, testin ölçtüğü şeyi ikiye
ayırıyor — `gercek_isi_yap`'ın kendi hesabının doğruluğu bir yanda, çağırdığı
bağımlılığın kaç kez ve hangi sırayla çağrıldığı öbür yanda. İkinci soru gerçek
bağımlılıkla sınanamaz, çünkü gerçek bağımlılığın kendi çağrı sayısını dışarıdan
gözlemlemenin bir yolu yoktur; sahte nesne bu gözlemi mümkün kılan tek araçtır.

`guduk_bagimlilik` ile alınan sonuç 43 — girdi ne olursa olsun sabit 42 döndüren güdüğün
üzerine `gercek_isi_yap`'ın eklediği 1. Bu satır çağrı sayısı hakkında hiçbir şey söylemiyor,
çünkü güdük zaten hiçbir şey saymıyor; yalnız `gercek_isi_yap`'ın kendi hesabının bağımlılığın
döndürdüğü değeri doğru işlediğini gösteriyor. Sahte nesneyle alınan sonuç 7 ile bu sonuç
karşılaştırıldığında, iki test aynı işlevi iki farklı soru için sınıyor: biri "doğru cevabı
üretiyor mu", öbürü "bağımlılığı doğru sayıda çağırıyor mu".

**PA7.** `duzenek_kur`/`duzenek_sok` çifti burada ebeveyn süreçte, `yalitilmis_calistir`'ın
çağrılmasından hemen önce ve hemen sonra çalıştırılıyor; testin kendisi hâlâ kendi
çocuğunda koşuyor, düzenek çocuğun içine taşınmıyor.

`duzenekli_calistir` satırı, düzeneğin sözleşmesini üç sayıyla doğruluyor. Kurulum sayacı 1,
temizlik sayacı 1 — yani her ikisi de tam olarak bir kez çalıştı, ne eksik ne fazla. Testin
kendisi `ortak_durum == 5` onaylamasını geçti, çünkü `duzenek_kur` bu değeri testin
çocuğundan **önce**, ebeveynde kurdu ve çocuk bu değeri fork ile devraldı. Son satır,
`duzenek_sok` çalıştıktan sonra ana süreçteki `ortak_durum`'un yeniden 0 olduğunu gösteriyor
— düzeneğin temizliği, testin çocuğu çoktan çıktıktan sonra ana süreç üzerinde gerçekleşiyor,
çünkü kurulum ve temizlik hiç çatallanmadan, doğrudan ebeveynde çalışıyor. Bu, düzeneğin
neden yalıtılmış koşumun **dışında** tutulması gerektiğini gösteriyor: düzenek her testten
önce ve sonra tekrarlanan ortak bir hazırlıktır, testin kendisi gibi her seferinde ayrı bir
çocuğa taşınması gerekmez — taşınsaydı, kurulumun etkisi (ortak_durum'un 5 olması) testin
çocuğuna hiç ulaşamazdı, çünkü her çocuk yalnız kendi fork anındaki durumu devralır.

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

Son iki satır, süreç yalıtımının nerede durduğunu gösteriyor. `test_dosyaya_yaz` kendi
çocuğunda `iz.dat` dosyasına bir değer yazıyor ve çocuk çıkıyor; bellek durumu onunla
birlikte kayboluyor. Ama `test_dosyadan_oku`, tamamen ayrı bir çocukta çalıştığı halde, aynı
dosyayı açıp önceki testin yazdığı değeri okuyabiliyor ve onaylama geçiyor. Çatallama yalnız
belleği kopyalar; dosya sistemi, sürecin dışında kalan bir nesnedir ve fork'un yalıttığı
alanın parçası değildir. İki test aynı dosyaya dokunuyorsa, aralarında bellek düzeyinde bir
yalıtım olsa bile birbirini etkileyebilirler.

Bu sınır yalnız dosyalarla sınırlı değil; süreç dışında yaşayan her paylaşılan kaynak aynı
sorunu taşır. Bir ağ soketi, bir kilit dosyası, işletim sisteminin kendisinde tuttuğu bir
ortam değişkeni ya da paylaşılan bellek bölgesi — bunların hiçbiri fork'un kopyaladığı
bellek alanının içinde değildir, dolayısıyla hiçbiri çatallanmayla otomatik olarak
yalıtılmaz. Bir test paketi yazarken bu ayrımı bilmek pratik bir sonuç doğurur: testleri
ayrı süreçlerde çalıştırmak, testlerin birbirinden bağımsız olduğunu **garanti etmez** —
yalnız bellek düzeyindeki bağımsızlığı garanti eder. Süreç dışı bir kaynağa dokunan testler
için ek bir önlem gerekir: ya kaynağın kendisi her test öncesi sıfırlanır, ya da testler
kaynağı hiç paylaşmayacak şekilde tasarlanır — örneğin her test kendi geçici dosya adını
üretir.

Dosya sistemi sınırının bir başka yüzü, testin **temizlenip temizlenmediğidir**. `iz.dat`
dosyası bu koşumdan sonra diskte kalmaya devam eder; bir sonraki koşumda `test_dosyaya_yaz`
zaten var olan bir dosyanın üzerine yazar, bu örnekte sonucu değiştirmez çünkü işlev dosyayı
her seferinde yeniden açıp yazıyor, ama farklı bir test aynı adı başka bir amaçla kullansaydı
eski koşumdan kalan içerik yeni testin ilk okuduğu değer olurdu. Bellekte bu sorun yoktur,
çünkü her koşum kendi sürecini sıfırdan başlatır ve önceki koşumun belleği işletim sistemi
tarafından geri alınır; dosya sistemi ise süreç sona erdikten sonra da değişmeden kalmaya
devam eder. Bu yüzden dosya kullanan testlerin sözleşmesi bellek kullanan testlerden bir
adım daha geniştir: yalnız testler arasında değil, **koşumlar arasında** da temiz bir
başlangıç noktası garanti edilmelidir — düzenek burada da devreye girebilir, temizlik
adımına dosyayı silmek eklenerek.

Bu genişletilmiş düzenek, `duzenek_sok`'un yalnız `ortak_durum`'u sıfırlamakla kalmayıp aynı
zamanda `remove("iz.dat")` çağırmasıyla kurulabilirdi; burada bu adım eklenmedi, çünkü amaç
dosya sisteminin fork'un dışında kaldığını göstermekti, dosya temizliğinin nasıl yapılacağını
değil. Genel kural şudur: bir düzenek yalnız kendi kurduğu kaynağı söker — `duzenek_kur`
`ortak_durum`'a dokunuyorsa `duzenek_sok` da yalnız onu geri alır; bir dosya oluşturan başka
bir düzenek olsaydı, onun temizliği de kendi dosyasını silmekle sınırlı kalırdı. Kurulumun
dokunmadığı bir kaynağı temizlik aşamasında değiştirmek, düzeneğin sözleşmesini genişletir ve
hangi testin hangi kaynaktan sorumlu olduğunu izlemeyi zorlaştırır.

#### Özet

- Bir test çerçevesi işlev işaretçisi tablosu, onaylama makrosu ve geçti/kaldı sayımından
  oluşur; her girdi tabloda bir satırdır.
- Her testi ayrı bir çocukta çatallayarak koşturmak, testin bellekte bıraktığı değişikliği
  sonraki teste taşımaz — bu dersin ölçtüğü sınıf **kopya**.
- Aynı testler yalıtımsız, tek süreçte koştuğunda birincinin değişikliği ikinciye sızar ve
  koşum sırası sonucu değiştirebilir.
- Bir bağımlılığı işlev işaretçisiyle bir sahte nesneye çevirmek, çağrı sayısını gerçek
  bağımlılığı değiştirmeden ölçmeyi sağlar; bir güdük ise çağrı saymadan yalnız sabit bir
  cevap döndürerek testin girdisini hazırlar — ikisi farklı amaçlara hizmet eder.
- Bir düzenek, kurulum ve temizliği testin kendisinden ayırır ve temizliğin testin
  sonucundan bağımsız olarak her zaman çalışmasını garanti eder; ebeveyn süreçte çalışır,
  testin çocuğuna taşınmaz.
- Süreç yalıtımı belleği keser, dosya sistemini kesmez; aynı dosyaya dokunan iki test
  çatallama ile birbirinden ayrılmaz. Bu sınır dosyalarla kalmaz — soket, kilit ve ortam
  değişkeni gibi süreç dışı her kaynak aynı şekilde yalıtım dışıdır.

#### Sonraki Adım

Bir test kaldığında, neyin hangi adımda beklenenden saptığını görmek gerekir. Bir sonraki
ders, çalışan bir programın belleğindeki ara değerleri, programı durdurup adım adım
ilerleterek nasıl görünür kıldığını ele alıyor.

### Hata Ayıklayıcı Kullanımı

Source: https://academia.sh/tr/kurslar/c-sistem-programlama/hata-ayiklayici-kullanimi

Bir kesme noktasının adımlar arasındaki ara değeri neden hiç görmediğini, gözetleme noktasının aynı değeri neden gördüğünü C içinde modelleyerek ölçer.

Bir test kaldığında hangi girdinin hangi adımda beklenenden saptığı bilinmez — yalnız
sonuç bilinir. Bir hata ayıklayıcı, çalışan bir programın ara değerlerini durdurarak ya
da izleyerek görünür kılar. Ama "görünür kılmak" tek bir anlam taşımıyor: bir hata
ayıklayıcının **neyi** gördüğü, ona nasıl durması söylendiğine bağlı.

Bir önceki ders, bir testin başarısız olduğunu gösteriyordu ama neden başarısız olduğunu
göstermiyordu. Onaylama kalırsa bilinen tek şey, koşulun yanlış olduğu; koşula giden
değerin hangi adımda o hale geldiği testin kendi çıktısında görünmez. Bir hata ayıklayıcı
bu boşluğu, programı istenen bir noktada durdurarak ya da bir değişkenin her değişimini
izleyerek doldurur. İki yaklaşım da aynı çalışan süreci gözlemliyor olsa bile, hangi
adımların görüldüğü stratejiye göre değişir — ve bu fark, bir hatayı ararken hangi
değerin görülüp hangisinin kaçırıldığını doğrudan belirler.

Bu ders üç durma stratejisini karşılaştırıyor: konuma göre duran (kesme noktası), her
değişimde duran (gözetleme noktası) ve bir duruma göre duran (koşullu kesme noktası).
Üçü de aynı çalışan süreci gözlemliyor, ama her biri farklı bir soruya cevap vermek için
kuruldu — "şu adıma gelince ne oluyor", "bu değer ne zaman değişiyor" ve "bu durum ne
zaman gerçekleşiyor". Bir dördüncü araç, çekirdek dökümü, bambaşka bir zamanda devreye
girer: süreç artık çalışmıyorken, geride bıraktığı durumu okur. Dört aracın ortak paydası,
hepsinin aynı temel soruyu — programın bir andaki iç durumu neydi — farklı koşullar altında
yanıtlamasıdır.

#### Kesme Noktası ve Gözetleme Noktası

Aşağıdaki model bir değişkene sırayla üç adımda üç değer yazan bir işlevi tanımlıyor.
Bu işlev iki farklı gözlem biçimiyle çalıştırılabiliyor: biri yalnız seçilmiş adımlarda
durup gözlemciyi çağırıyor, diğeri her adımda duruyor.

```c
// adim.h — uc adimlik yazma dizisi ve iki gozlem bicimi
#ifndef ADIM_H
#define ADIM_H

typedef void (*GozlemIslevi)(int adim, int deger);

/* Uc adimda bir degiskene sirayla 1, 2, 3 yazar. kesme_noktalari dizisinde
   olmayan bir adimda gozlemci hic cagrilmaz -- program o adimda durmadan
   ilerler. */
void kesme_noktasiyla_calistir(GozlemIslevi gozle, const int *kesme_noktalari, int sayisi);

/* Ayni uc adimlik yazma dizisi, ama gozlemci HER adimda cagrilir -- kesme
   noktasi listesi yok, cunku her yazma bir durma nedenidir. */
void gozetleme_noktasiyla_calistir(GozlemIslevi gozle);

/* Adim listesi yerine deger uzerinde bir kosul tanimlanir: gozlemci yalnizca
   deger esik'e ulastiginda cagrilir. Hangi adimda durulacagi onceden bilinmez,
   yalnizca hangi kosulda durulacagi bilinir. */
void kosullu_kesme_noktasiyla_calistir(GozlemIslevi gozle, int esik);

#endif
```

```c
// adim.c — kesme noktasi secici durur, gozetleme noktasi her yazmada durur
#include "adim.h"

static int kesme_noktasinda_mi(int adim, const int *noktalar, int sayisi) {
    for (int i = 0; i < sayisi; i++) {
        if (noktalar[i] == adim) return 1;
    }
    return 0;
}

void kesme_noktasiyla_calistir(GozlemIslevi gozle, const int *kesme_noktalari, int sayisi) {
    for (int adim = 0; adim < 3; adim++) {
        int deger = adim + 1;   /* adim 0 -> 1, adim 1 -> 2, adim 2 -> 3 */
        if (kesme_noktasinda_mi(adim, kesme_noktalari, sayisi)) {
            gozle(adim, deger);
        }
    }
}

void gozetleme_noktasiyla_calistir(GozlemIslevi gozle) {
    for (int adim = 0; adim < 3; adim++) {
        int deger = adim + 1;
        gozle(adim, deger);   /* her yazmada duruyor, secim yok */
    }
}

void kosullu_kesme_noktasiyla_calistir(GozlemIslevi gozle, int esik) {
    for (int adim = 0; adim < 3; adim++) {
        int deger = adim + 1;
        if (deger >= esik) {   /* adim numarasina degil, degerin kendisine bakiyor */
            gozle(adim, deger);
        }
    }
}
```

**PA8.** Adımlama burada tek bir döngünün ilerleyişi olarak modelleniyor: her döngü
turu bir adımı, gözlemcinin çağrılıp çağrılmaması ise o adımda durulup durulmadığını
temsil ediyor. Gerçek bir hata ayıklayıcı adım adım ilerlemeyi kullanıcının komutuyla
tetikler; burada zamanlamayı görünür kılmak için tek bir çalışma akışında yürütülüyor.

**PA9.** Koşul burada tek bir tamsayı karşılaştırmasına (`deger >= esik`) indirgeniyor;
gerçek bir hata ayıklayıcıda koşul rastgele bir boole ifadesi olabilir, ama mekanizma
aynıdır — her adımda koşul değerlendirilir, yalnız sağlandığında durulur.

Kesme noktası ile koşullu kesme noktası arasındaki fark, **neyin önceden bilindiğidir**.
Sıradan bir kesme noktası belirli adım numaralarında durur — hangi adımların ilginç
olduğu önceden bilinmelidir. Koşullu bir kesme noktası ise adım numarasına hiç bakmaz;
yalnız değerin kendisine bakar ve koşul her sağlandığında durur, kaçıncı adımda
olduğuna bakılmaksızın. Bu, "üçüncü adımda dur" ile "değer 2'ye ulaştığında dur"
arasındaki farktır — birincisi konuma, ikincisi duruma bakar. Konum önceden
bilinmediğinde ama durumun ne olması gerektiği biliniyorsa (örneğin bir sayacın belirli
bir eşiği geçtiği an), koşullu kesme noktası sıradan bir kesme noktasının yapamayacağı
bir şeyi yapar: adım sayısı yüzlerce ya da binlerce olsa bile, aranan durum yalnız bir
kez gerçekleştiğinde durur.

```c
// olcum.c — kesme noktasi 2'yi atliyor, gozetleme noktasi 2'yi goruyor
#include <stdio.h>
#include "adim.h"

static int gorulen_sayisi = 0;
static int gorulen_degerler[3];

static void kaydet(int adim, int deger) {
    (void)adim;
    gorulen_degerler[gorulen_sayisi] = deger;
    gorulen_sayisi++;
}

int main(void) {
    int noktalar[2] = { 0, 2 };   /* kesme noktalari 0. ve 2. adimda */

    kesme_noktasiyla_calistir(kaydet, noktalar, 2);
    printf("kesme noktasiyla gorulen sayisi: %d\n", gorulen_sayisi);
    printf("kesme noktasiyla ilk gorulen: %d\n", gorulen_degerler[0]);
    printf("kesme noktasiyla ikinci gorulen: %d\n", gorulen_degerler[1]);

    gorulen_sayisi = 0;
    gozetleme_noktasiyla_calistir(kaydet);
    printf("gozetleme noktasiyla gorulen sayisi: %d\n", gorulen_sayisi);
    printf("gozetleme noktasiyla ikinci gorulen: %d\n", gorulen_degerler[1]);

    gorulen_sayisi = 0;
    kosullu_kesme_noktasiyla_calistir(kaydet, 2);   /* deger 2'ye ulasinca dur */
    printf("kosullu kesme noktasiyla gorulen sayisi: %d\n", gorulen_sayisi);
    printf("kosullu kesme noktasiyla ilk gorulen: %d\n", gorulen_degerler[0]);
    printf("kosullu kesme noktasiyla ikinci gorulen: %d\n", gorulen_degerler[1]);
    return 0;
}
```

```text
kesme noktasiyla gorulen sayisi: 2
kesme noktasiyla ilk gorulen: 1
kesme noktasiyla ikinci gorulen: 3
gozetleme noktasiyla gorulen sayisi: 3
gozetleme noktasiyla ikinci gorulen: 2
kosullu kesme noktasiyla gorulen sayisi: 2
kosullu kesme noktasiyla ilk gorulen: 2
kosullu kesme noktasiyla ikinci gorulen: 3
```

Kesme noktaları 0. ve 2. adımda kuruldu; program bu iki adımda durdu ve sırasıyla 1 ile
3 değerlerini gördü. Aradaki 1. adımda değişken 2 değerini taşıdı, ama orada kesme
noktası olmadığı için gözlemci hiç çağrılmadı — 2 değeri hiç görülmedi. Aynı üç adımlık
dizi gözetleme noktasıyla çalıştırıldığında gözlemci her adımda çağrıldı: üç değer de
kaydedildi ve ikinci gözlem tam olarak kesme noktasının kaçırdığı 2 değeriydi.

Son üç satır koşullu kesme noktasının davranışını gösteriyor. Eşik 2 olarak verildiğinde,
0. adımdaki değer (1) eşiğin altında kaldığı için gözlemci orada hiç çağrılmadı; 1. ve 2.
adımlardaki değerler (2 ve 3) eşiğe ulaştığı ya da geçtiği için ikisinde de durdu. Görülen
sayı 2, ilk görülen 2, ikinci görülen 3 — sıradan kesme noktasının 0. ve 2. adımda durup
1 ile 3'ü görmesinden farklı bir küme. İki mekanizma da aynı üç adımlık diziyi tarıyor,
ama biri **konuma** göre seçim yapıyor, diğeri **duruma** göre; bu yüzden aynı diziden
farklı gözlem kümeleri çıkarıyorlar. Bu fark, hangi mekanizmanın seçileceğini önceden
bilinen bilginin türü belirliyor: aranan adım biliniyorsa kesme noktası yeterli ve daha
ucuzdur, aranan yalnız bir durum (bir değerin belirli bir eşiği geçmesi gibi) ise ve
hangi adımda gerçekleşeceği bilinmiyorsa koşullu kesme noktası gerekir.

Gözetleme noktasıyla koşullu kesme noktası birbirine benzer görünse de aynı şey değildir.
Gözetleme noktası **her** yazmada durur, seçim yapmaz — üç değerin üçünü de görmesinin
nedeni budur. Koşullu kesme noktası ise yalnız koşul sağlandığında durur; eşik 2 yerine 4
verilseydi, hiçbir adımda durmazdı, çünkü dizideki en büyük değer 3'tür ve hiçbiri 4'e
ulaşmaz. Gözetleme noktası "hiç kaçırma" garantisi verirken bunun bedelini her adımda
durarak öder; koşullu kesme noktası bu garantiyi vermez — yalnız koşulu tanımlanan durumu
kaçırmaz, koşulun dışında kalan her şeyi sıradan kesme noktası gibi atlar. Üçü arasındaki
seçim, aranan şeyin bir **konum** mu, her **değişim** mi, yoksa belirli bir **durum** mu
olduğuna bağlıdır.

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

Bir program beklenmedik şekilde sonlandığında, o andaki bellek durumunun bir dökümü
sonradan incelenebilir — programı yeniden çalıştırmadan, çökme anındaki yığıt ve
değişken değerlerine bakmak için. Bu döküm burada üretilmiyor: bir sürecin gerçekten
çökertilmesi bu dersin kapsamı dışında, yalnız ne için kullanıldığı anlatılıyor. Kesme
noktası ve gözetleme noktası **çalışan** bir süreci durdurup gözlemler; döküm ise
**durmuş** bir sürecin geride bıraktığı durumu, süreç artık yokken okur — ikisi aynı
görünürlük sorusuna iki farklı zamanda cevap verir.

Çekirdek dökümünün asıl değeri, gözlemcinin **hazır olmadığı** anlar içindir. Kesme
noktası, gözetleme noktası ve koşullu kesme noktasının üçü de, hatayı arayan kişinin
programı önceden bir hata ayıklayıcının altında başlatmasını ya da sonradan ona
bağlanmasını gerektirir — programın ne zaman ve nerede duracağı bir ölçüde önceden
tahmin edilmelidir. Üretimde çalışan, kimsenin izlemediği bir süreç beklenmedik bir anda
çökerse, o anı yeniden yaratmak çoğu zaman mümkün olmaz; girdi, zamanlama ya da sistem
durumu bir daha aynı şekilde tekrarlanmayabilir. İşletim sistemi böyle bir çökme anında,
sürecin o anki bellek görüntüsünü — yığıt, öbek ve yazmaç değerlerini — bir dosyaya
yazabilir. Bu dosya sonradan, süreç artık çalışmıyorken bir hata ayıklayıcıya verilir ve
çökme anındaki değişken değerleri, çağrı zinciri ve durum, sanki program hâlâ o anda
duruyormuş gibi incelenebilir — yalnız artık ileri adımlanamaz, çünkü döküm tek bir
donmuş kareden ibarettir, çalışan bir süreç değildir.

Bu, dökümü kesme ve gözetleme noktalarından ayıran temel farkı ortaya koyuyor: kesme ve
gözetleme noktaları **önceden kurulmuş bir gözlem** gerektirir, döküm ise **gözlem hiç
kurulmamış olsa bile** geriye bir kayıt bırakır. Bir hata yeniden üretilebiliyorsa ve
nerede durulacağı biliniyorsa kesme ya da gözetleme noktası tercih edilir, çünkü canlı
bir süreç üzerinde adım adım ilerlemeye izin verir. Hata yalnız bir kez, öngörülemeyen
bir anda ve gözlemci hazır değilken oluyorsa, geriye kalan tek araç dökümdür — çökme
anının kendisini yeniden yaratmaya gerek kalmadan, o anın donmuş bir kopyasını sonradan
okumaktır.

Bu modelin kendi sınırı da var: burada gözlemlenen değer, kaynak kodun kendisinde
yazan değerle birebir aynı kalacak şekilde kuruldu; eniyilenmiş bir programda hata
ayıklayıcının gördüğü değer kaynaktakiyle aynı olmayabilir — bir değişken yazmaçta
tutulmuş ya da tamamen silinmiş olabilir.

Kesme noktası ile gözetleme noktası arasındaki fark, bu dersin modelinde bir dizi
uzunluğuyla sınırlı görünse de, gerçek bir programda çok daha büyük bir aralıkta
gizli kalabilir. Üç adımlık bir dizide aradaki tek değer gözden kaçtığında bunu fark
etmek görece kolaydır; yüzlerce adımdan geçen bir döngüde, kesme noktası yalnızca
girişte ve çıkışta kurulmuşsa, aradaki hatalı bir ara değer koşum boyunca hiç
görülmeden kalabilir. Gözetleme noktasının her yazmada durması bu riski ortadan
kaldırır, ama bunun bir bedeli vardır: bir değişken sık sık yazılıyorsa, program her
yazmada durduğu için çok daha yavaş ilerler. Kesme noktası ile gözetleme noktası
arasındaki seçim bu yüzden yalnız "hangisi daha çok görür" sorusu değil, "hangi
adımı görmek gerektiği önceden ne kadar biliniyor" sorusudur — hiç bilinmiyorsa
gözetleme noktasının yavaşlığı, kesme noktasının kaçırma riskine tercih edilir.

Üç durma stratejisi tek bir hata avında art arda da kullanılabilir. Bir toplam
değişkeninin beklenmedik bir noktada negatife düştüğü varsayılsın. İlk adım genellikle
gözetleme noktasıdır: değişkenin her yazıldığı an durulur, birkaç koşumdan sonra hangi
aralıkta işaretin döndüğü görülür. Aralık daraldıkça gözetleme noktasının bedeli — her
yazmada durmanın getirdiği yavaşlık — artık gerekli değildir; o aralığın başına sabit bir
kesme noktası konur ve program yalnız orada durdurulup adım adım ilerlenir. Değer negatife
dönüşü tek bir koşulda değil, girdiye bağlı birkaç farklı koşulda tekrarlıyorsa, kesme
noktası yerini koşullu kesme noktasına bırakır: durma artık "şu satıra gelince" değil
"toplam sıfırın altına düşünce" olarak tanımlanır, hangi girdinin bunu tetiklediğinden
bağımsız olarak. Bu üç aracın sırayla kullanılması rastgele değildir — her adım bir
öncekinin bulduğu bilgiyi (yaklaşık konum, sonra kesin konum, sonra durumun kendisi)
daraltarak bir sonrakine devreder. Hata üretimde ve yalnız bir kez, kimsenin izlemediği bir
anda ortaya çıkmışsa bu sıralı avlanma hiç başlamaz; elde kalan tek şey çekirdek dökümüdür,
ve o zaman soru "nerede durulmalı" değil "geride ne kayıt kaldı"dır.

#### Özet

- Kesme noktası, yalnız seçilmiş adımlarda durur; iki kesme noktası arasında geçen bir
  ara değer hiç gözlemlenmez — bu dersin ölçtüğü sınıf **gözlenmeyen**.
- Gözetleme noktası her yazmada durur; kesme noktasının kaçırdığı ara değeri de görür,
  bu bir karşıtlık modelidir.
- Koşullu kesme noktası adım numarasına değil değerin kendi durumuna bakar; konum
  bilinmese de aranan durum tanımlanabiliyorsa kesme noktasının yapamadığını yapar.
- Çekirdek dökümü çalışan bir süreci durdurmaz, sonlanmış bir sürecin durumunu sonradan
  okur; önceden bir gözlem kurulmamış olsa bile geriye bir kayıt bırakır.
- Bu modelde gözlemlenen değer kaynaktaki değerle eşleşecek şekilde kuruldu; eniyilenmiş
  bir programda bu eşleşme garanti değildir.
- Hangi durma stratejisinin seçileceği aranan şeyin bir konum mu, her değişim mi, yoksa
  belirli bir durum mu olduğuna bağlıdır; gerçek bir avda üçü art arda da kullanılır —
  gözetleme noktasıyla kabaca daraltılan aralık kesme noktasıyla kesinleştirilir,
  koşula bağlı tekrar eden bir durum ise koşullu kesme noktasıyla yakalanır.

Kesme noktası ile gözetleme noktasının ikisi de programı **durdurarak** çalışır: program
duruncaya kadar normal hızında ilerler, durduğu anda bellek durumu dondurulmuş gibi
okunabilir hale gelir. Bu, iki yöntemi çekirdek dökümünden ayıran ikinci bir eksendir —
döküm sürecin kendisi artık çalışmıyorken alınan tek bir kareyken, kesme ya da gözetleme
noktası çalışan sürecin üzerinde birden çok kareyi art arda gösterebilir. Bir hatayı
ararken hangisinin daha uygun olduğu, hatanın **tek bir anda mı** yoksa **bir dizi adım
boyunca mı** ortaya çıktığına bağlıdır: tek bir çökme anı dökümle, adımlar arasında
biriken bir sapma ise durma noktalarıyla incelenir.

#### Sonraki Adım

Bir değişkenin kaynakta yazdığı değerle çalışma zamanında taşıdığı değer her zaman aynı
kalmaz — bir sonraki ders bu farkın nereden geldiğini ele alıyor.

### Optimizasyon Düzeyleri

Source: https://academia.sh/tr/kurslar/c-sistem-programlama/optimizasyon-duzeyleri

Eniyileyicinin okunmayan bir yazmayı sildiği model üzerinden as-if kuralı, volatile'ın verdiği garanti ve kursun on beş dersinin toplu dökümü.

Bir önceki ders, hata ayıklayıcının gördüğü değerin kaynaktaki yazımla her zaman
aynı olmadığını söyleyerek kapandı: bir değişken yazmaçta tutulmuş ya da
tamamen silinmiş olabilir. Bu ders o farkın nereden geldiğini ele alıyor.
Eniyileyici, gözlenebilir davranışı değiştirmeyen bir yazmayı kaldırabilir —
kaldırdığı yazma hiçbir okuyucuya ulaşmaz. Kurs bunu tek bir soruyla ölçüyordu:
bu değişikliği kim görüyor? Eniyileme için yanıt, geri kalan on dört değişiklikten
farklı bir yoldan **gözlenmeyen**e varıyor: burada ne bir kopyalama var ne de bir
aracı. Yazma vardır, ama hiçbir okuma ona ulaşmadan üstüne başka bir yazma gelir.

#### Okunmadan Ezilen Yazma

Bir akışı üç adımla düşünün: bir yere 1 yazılır, aynı yere 2 yazılır, sonra o
konum bir yerde okunur. Birinci yazmanın etkisi, ikinci yazma gelene kadar hiç
okunmadıysa, ikinci yazma geldiğinde birinci yazmanın konumdaki izi tamamen
kaybolmuştur. Programın davranışını gözleyen hiçbir taraf — ne bir dosyaya
yazılan çıktı, ne bir başka sürece giden bir değer, ne de bir kilitle korunan
paylaşılan durum — birinci yazmanın hiç var olup olmadığını ayırt edemez.
Program birinci yazmayı hiç yapmasa da gözlenen sonuç değişmez.

**PA10.** Bir eniyileyici modeli, ardışık iki yazma arasında hiçbir okuma
olmadığını tespit ettiğinde birincisini kaldırabilir; kaldırmasa da gözlenebilir
sonuç aynıdır.

Ölçüm, kursun çekirdeğiyle aynı kayıt biçimini kullanıyor:

```c
// gozlem.h — bir degisikligin kime ulastigini siniflayan kayit
#ifndef GOZLEM_H
#define GOZLEM_H

enum Sinif { KOPYA, ORTAK, AKTARIM, GOZLENMEYEN };

/* Bir degisikligi kaydeder: gordu, degisikligi yapmayan tarafin onu
   okuyup okuyamadigidir (1 ya da 0). */
void gozlem(const char *degisiklik, enum Sinif sinif, int gordu);

/* Sinif basina sayilari ve ulasan / ulasmayan toplamini basar. */
void gozlem_ozeti(void);

#endif
```

```c
// gozlem.c — sayilar yalniz bu birimde tutulur
#include <stdio.h>
#include "gozlem.h"

static const char *const adlar[] = { "kopya", "ortak", "aktarim", "gozlenmeyen" };
static int sayilar[4];
static int ulasan = 0;
static int ulasmayan = 0;

void gozlem(const char *degisiklik, enum Sinif sinif, int gordu) {
    int beklenen = (sinif == ORTAK || sinif == AKTARIM);
    sayilar[sinif]++;
    if (gordu) {
        ulasan++;
    } else {
        ulasmayan++;
    }
    printf("%-34s %-8s %s%s\n", degisiklik, gordu ? "gordu" : "gormedi",
           adlar[sinif], gordu == beklenen ? "" : "  TUTARSIZ");
}

void gozlem_ozeti(void) {
    printf("siniflar: kopya %d, ortak %d, aktarim %d, gozlenmeyen %d\n",
           sayilar[KOPYA], sayilar[ORTAK], sayilar[AKTARIM], sayilar[GOZLENMEYEN]);
    printf("ulasan %d, ulasmayan %d\n", ulasan, ulasmayan);
}
```

Aşağıdaki kesit, kursun çekirdeğinden alınıyor ve bu dersin kendi kesitidir —
adları ve gövdesi kurs boyunca değişmez:

```c
// eniyileyici_olcumu.c — gorur.c'deki eniyileme kesiti, degistirilmeden
#include <stdio.h>
#include "gozlem.h"

struct Adim {
    char islem;
    int deger;
};
static void eniyileme(void) {
    struct Adim akis[] = { { 'y', 1 }, { 'y', 2 }, { 'o', 0 } };
    int okuyan = 0;
    int son_yazma = -1;
    for (int i = 0; i < 3; i++) {
        if (akis[i].islem == 'y') {
            son_yazma = i;
        } else if (son_yazma == 0) {
            okuyan++;
        }
    }
    gozlem("okunmadan ezilen yazma", GOZLENMEYEN, okuyan > 0);
}

int main(void) {
    eniyileme();
    gozlem_ozeti();
    return 0;
}
```

```text
okunmadan ezilen yazma             gormedi  gozlenmeyen
siniflar: kopya 0, ortak 0, aktarim 0, gozlenmeyen 1
ulasan 0, ulasmayan 1
```

`akis` dizisi üç adımı taşıyor: `y` bir yazma, `o` bir okuma. Döngü, her okuma
adımında en son hangi yazmanın etkili olduğunu (`son_yazma`) yokluyor. Okuma
geldiğinde `son_yazma` hâlâ **sıfırıncı** yazmayı gösteriyorsa (`son_yazma == 0`),
o okuma birinci yazmanın değerini görmüş sayılır ve `okuyan` artıyor. Bu
modelde `akis` sırasıyla `y=1`, `y=2`, `o` içeriyor: ikinci adım birinciyi
`son_yazma`yı 1'e taşıyarak ezmiş oluyor; üçüncü adımdaki okuma geldiğinde
`son_yazma` artık 0 değil, 1. Birinci yazmayı gören hiçbir okuma yok — `okuyan`
sıfırda kalıyor. `gozlem` çağrısı bunu `GOZLENMEYEN` sınıfıyla ve `gordu`
alanına `okuyan > 0` (yani 0, yanlış) vererek kaydediyor.

Bu, bir eniyileyicinin okunmayan bir yazmayı neden **silebileceğinin** modelidir:
birinci yazma hiç yapılmasa da `son_yazma` dizisi ve dolayısıyla `okuyan` sayısı
aynı kalır. Program iki yazma yerine tek yazma içerseydi — doğrudan 2 yazsaydı —
gözlenebilir sonuç (üçüncü adımın ne okuduğu) değişmezdi. Model bu yüzden
`okunmadan ezilen yazma`yı `gözlenmeyen` sınıfına koyuyor: değişiklik yapılmış,
ama hiçbir okuyucuya ulaşmamış.

#### As-if Kuralı

Bir eniyileyicinin bir yazmayı silme hakkı keyfi değildir; tek bir koşula bağlıdır:
**as-if kuralı** ("sanki kuralı"). Bu kural, bir programın çalışma zamanı davranışı
**gözlenebilir davranış** kümesi üzerinden tanımlanır ve eniyileyici bu kümeyi
koruduğu sürece programın adım adım nasıl çalıştığını istediği gibi değiştirebilir
— sanki kaynaktaki sırayla çalışıyormuş gibi görünmesi yeterlidir, gerçekten öyle
çalışması gerekmez. Gözlenebilir davranış üç şeyle sınırlıdır: girdi/çıktı
işlemlerinin sırası ve içeriği, `volatile` işaretli nesnelere yapılan erişimler ve
programın sonlanma durumu. Yukarıdaki modelde birinci yazma bunların hiçbirine
girmiyor: bir dosyaya yazılmıyor, `volatile` değil, programın sonlanmasını
etkilemiyor. Bu yüzden silinmesi as-if kuralını ihlal etmez.

**PA11.** Bir yazma ancak gözlenebilir davranışın **hiçbir** parçasına
girmiyorsa okunmadan ezilebilir; girdiği an bu hak düşer.

As-if kuralı çekirdeğin diğer on dört ölçümüyle çelişmiyor, onları tamamlıyor.
`fork` sonrası çocuğun değiştirdiği değişken (kopya sınıfı) as-if kuralına konu
değildir — o, iki ayrı belleğin varlığıyla ilgilidir, tek bir sürecin içindeki
yazma sırasıyla değil. İşleyicinin kaldırdığı bayrak (aktarım sınıfı) ise tam
tersi yönden as-if kuralına giriyor: `volatile sig_atomic_t` işaretli olduğu için
eniyileyici o yazmaya asla dokunamaz — bir sonraki bölüm bunu ayrıntılandırıyor.

#### Volatile'ın Verdiği Garanti

`volatile` anahtar sözcüğü bir değişkene uygulandığında, derleyiciye o değişkene
yapılan her okuma ve yazmanın gözlenebilir davranışın bir parçası sayılmasını
söyler. Bunun sonucu doğrudandır: `volatile` işaretli bir yazma, ardından hiçbir
okuma gelmese bile silinemez; `volatile` işaretli bir okuma, sonucu hiçbir yerde
kullanılmasa bile atlanamaz. Sinyaller dersindeki `bayrak` değişkeni tam olarak bu
yüzden `volatile sig_atomic_t` olarak tanımlanmıştı: bir işleyicinin kaldırdığı
bayrağı ana akış okumadan önce eniyileyici o yazmayı "kimse okumuyor" diye
silebilseydi, aktarım hiç gerçekleşmezdi.

Modelin diliyle söylenirse: `akis` dizisindeki konum `volatile` işaretli olsaydı,
birinci yazma okunmadan ezilse de silinemezdi, çünkü yazmanın kendisi gözlenebilir
davranışın parçası sayılırdı. Bu, kaydın sınıfını değiştirmez — ikinci yazma
geldiğinde birinci yazmanın değerini yine hiçbir okuma görmemiştir ve kayıt yine
`gözlenmeyen`dir. Değişen tek şey eniyileyicinin hakkıdır: yazma programda kalır,
yalnız okuyucuya ulaşmaz. Ders bunu ayrı bir kodla ölçmüyor; `volatile`'ın etkisi
eniyileyicinin ürettiği kodda görünür ve kaynak metindeki bir model o kodu
basamaz. Bu dersin `main` işlevi `eniyileme`'yi ve ardından `gozlem_ozeti`'yi
çağırıyor; çıktı, `gormedi` ile işaretlenmiş tek kaydın `gözlenmeyen` sınıfına
düştüğünü gösteriyor.

`volatile`'ın verdiği garanti dardır ve genişletilmemelidir. Tek başına iş
parçacıkları arasında sıralama sağlamaz, atomiklik sağlamaz, bellek bariyeri
kurmaz — POSIX İş Parçacıkları dersindeki kilit ve birleştirme (`join`) hâlâ
gereklidir. `volatile`'ın verdiği tek şey, o nesneye yapılan erişimlerin
eniyileyici tarafından silinmeyeceği ve sırasının korunacağı garantisidir; bu,
bir sinyal işleyicisi ile ana akış arasındaki tek paylaşım biçiminin neden
`volatile sig_atomic_t` olduğunu açıklar.

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

Model, bir yazmanın silinip silinmeyeceğine tek bir kurala — gözlenebilir
davranışın korunup korunmadığına — bakarak karar veriyor. Gerçek bir eniyileyici
bu kararı çok daha geniş bir bilgiyle verir: bir değişkenin adresi alınmış mı,
başka bir çeviri biriminden görünür mü, bir işaretçi üzerinden takma adlanmış
olabilir mi. Bu ders eniyileme düzeyini bir bayrakla yazmıyor,
derleyici çıktısını, üretilen kodu ya da bir ölçümün süresini basmıyor — çünkü
bunların hiçbiri kaynak metindeki bir modelle gösterilemez; gerçek bir aracın
gerçek kararıdır. Model yalnız kuralın kendisini gösteriyor: hangi yazmaların
silinebileceğini, hangilerinin silinemeyeceğini. Bir yazmanın adresi başka bir
işlevce tutuluyorsa, o işlev geri çağrıldığında konumu okuyabilir; bu durumda
yazma artık gözlenebilir davranışın bir parçasıdır ve modeldeki gibi sessizce
silinemez. Modelin bunu göstermemesi bir eksiklik değil, sınırdır: kesit yalnız
tek bir akış içindeki ardışık yazmaları ele alıyor, çapraz işlev görünürlüğünü
değil.

#### Özet

- `okunmadan ezilen yazma` (model): bir konuma art arda yapılan iki yazmadan
  birincisi hiç okunmadan ikincisi geliyorsa, birincinin izi kalmıyor.
- As-if kuralı, eniyileyicinin bir yazmayı ancak gözlenebilir davranışı
  değiştirmeyecekse silebileceğini söylüyor; gözlenebilir davranış girdi/çıktı,
  `volatile` erişimleri ve sonlanma durumuyla sınırlı.
- `volatile`, bir nesneye yapılan her erişimi gözlenebilir davranışın parçası
  sayar; bu yüzden `volatile` işaretli bir yazma hiç okunmasa bile silinmez.
- Sinyaller dersindeki `volatile sig_atomic_t` bu garantiyi kullanıyordu: bir
  işleyicinin kaldırdığı bayrak, ana akış onu henüz okumamış olsa da eniyileyici
  tarafından silinemez.
- Bu dersin ölçtüğü sınıf **gözlenmeyen**: yazma yapılıyor ama hiçbir okuyucuya
  ulaşmıyor — ne yazanın kendi tarafında kalıyor (kopya), ne bir aracıdan geçip
  bir kez görünüyor (aktarım); ezilip kayboluyor.
- Model gerçek bir eniyileyicinin kararını değil, kararın dayandığı kuralı
  gösteriyor; adres alma ve çapraz işlev görünürlüğü modelin kapsamı dışında.

#### Kurs Kapanışı

Kurs on beş ders boyunca tek bir soruyu sordu: **bu değişikliği kim görüyor?**
Bu ders o soruyu son kez yanıtlıyor ve yanıt geri kalan on dördünden farklı bir
yoldan gözlenmeyene varıyor — yazma var, okuyucu yok, çünkü bir sonraki yazma
araya girmeden okuma hiç gelmedi. Aşağıdaki tablo on beş değişikliğin tümünü
topluyor:

| Ders | Ölçülen değişiklik | Kim görüyor | Sınırlayıcı ölçüm |
|---|---|---|---|
| Dosya Tanıtıcıları | `dup` ile çoğaltılan tanıtıcının paylaştığı konum | ortak | Tanıtıcı numarası neyin paylaşıldığını söylemez; paylaşım yalnız konum davranışıyla ölçülür |
| Süreç Yönetimi | `fork` sonrası çocuğun değiştirdiği değişken | kopya | Ölçüm zamanlamaya bağlı değildir; bir yarışı değil, iki kopyanın bağımsız olduğunu ölçer |
| Sinyaller | İşleyicinin kaldırdığı bayrak | aktarım | Engellenmişken tekrar gelen aynı sinyal açılışta yalnız bir kez teslim edilir |
| Süreçler Arası İletişim | Boruya yazılan değer | aktarım | Boru bir akıştır, ileti sınırı taşımaz; sınır ayrı bir uzunluk önekiyle kurulur |
| POSIX İş Parçacıkları | İş parçacığının kilit altında yazdığı sayaç | ortak | Yalnız son değer doğrulanır; ara durumlar ve yazma sırası ölçülmez |
| Bağlı Yapılar | Takma adla değişen düğüm | ortak | Bir işaretçinin paylaşımı kaynakta görünmez; iki başın aynı düğümü gösterip göstermediği eşitlikle ölçülür |
| Halka Arabellek ve Kuyruklar | Halkaya konan öge | aktarım | Okuma işlevinin imzası değerin yapıda kalıp kalmadığını söylemez; bu, art arda iki okumayla ölçülür |
| Karma Tabloları | Eklendikten sonra değişen anahtar | kopya | Ekleme işlevinin imzası anahtarın kopyalanıp kopyalanmadığını söylemez; anahtar işaretçiyle tutulsaydı sınıf ortak olurdu |
| Nesne Benzeri Tasarım | Bir kullanıcının yaptığı yatırma | ortak | Arayüzün biçimi (opak tip, işlev işaretçileri, düz işlevler) paylaşımı belirlemez; yalnız işaretçi eşitliği belirler |
| Kaynak Temizleme Deyimleri | Boşaltılmadan çıkılan tampon | gözlenmeyen | Gözlenmeyen, hiçbir yere ulaşmamak demektir; bu, değer basılmadan `== NULL` denetimleriyle doğrulanır |
| Derleme Sistemleri | Bağımlılığı yazılmamış başlık | kopya | Model yalnız yazılmış bağımlılığı görür; gerçekte hangi başlığın okunduğunu kendi başına bilmez |
| Kütüphane Üretimi | Statik bağlı kütüphane güncellemesi | kopya | Model yalnız hangi değere ulaşıldığını ölçer; arayüz değişirse işaretçiye dayalı güvence kaybolur ve bu durum koşturulmaz |
| Test Çerçeveleri | Yalıtılmış testin değişikliği | kopya | Süreç yalıtımı bellek değişikliğini keser, dosya sistemi değişikliğini kesmez |
| Hata Ayıklayıcı Kullanımı | İki durak arasındaki ara değer | gözlenmeyen | Kesme ve gözetleme noktası çalışan bir süreci gözler, çekirdek dökümü durmuş olanı okur; döküm üretilmez |
| Optimizasyon Düzeyleri | Okunmadan ezilen yazma | gözlenmeyen | Model yalnız tek akış içindeki ardışık yazmaları ele alır; adres alma kapsam dışı |

**5 kopya · 4 ortak · 3 aktarım · 3 gözlenmeyen.**

Bu dağılım, çekirdeğin on beş gözleminin dağılımıyla aynıdır — beklenmedik bir
uyuşma değil, çünkü çekirdek her ders için tam olarak bir gözlem taşıyor ve her
ders kendi kesitini o gözlemden alıyor. Yedi değişiklik öteki tarafa ulaştı, sekizi
ulaşmadı; ulaşanlar bir aracıdan (aktarım) ya da paylaşılan bir nesneden (ortak)
geçti, ulaşmayanlar ya yazanın kendi tarafında kaldı (kopya) ya da hiçbir yerde
durmadı (gözlenmeyen). Bu ayrım kursun başından beri aynı kaldı: bir değişikliğin
**yapılmış olması**, onun **görülmüş olmasını** garanti etmez.

Kurs boyunca aynı soru farklı düzeylerde tekrar sorulmuş oldu — iki sürecin belleği
arasında, bir işleyici ile ana akış arasında, iki iş parçacığının paylaştığı
bellekte, bir derleme biriminin başka birine bıraktığı izde ve son olarak bir
eniyileyicinin sildiği tek bir yazmada. Her düzeyde yanıt aynı dört sınıftan
birine düştü; sınıfların sayısı değişmedi, yalnız hangi düzeneğin hangi sınıfa
girdiği değişti.

Bir sonraki kurs (C++ Temelleri ve Nesne Tasarımı) sınıflar ve kaynak yönetimi
konusunu ele alıyor; kendi ölçü eksenini kendi ilk dersinde kuracak.

