---
title: 'Hata Yönetimi Kalıpları'
source: 'https://academia.sh/tr/kurslar/c-bellek/hata-yonetimi-kaliplari'
course: "C'de Bellek ve İşaretçiler"
language: tr
updated: '2026-09-26T03:02:43+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Kısmi başarısızlıkta ayrılmış bir bloğu işlevin kendisinin bırakmasını dönüş koduyla ölçer'
    - 'errno tabanlı hata bildirimini yalnız sıfır olup olmadığını basarak kullanır'
    - 'Birden çok kaynağı tek çıkış noktalı bir goto temizle kalıbıyla temizler'
---

# Hata Yönetimi Kalıpları

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

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

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

## Hata Yolunda Temizlik

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

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

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

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

#include <stddef.h>

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

#endif
```

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

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

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

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

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

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

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

#include <stddef.h>

typedef struct Kayit Kayit;

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

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

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

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

#endif
```

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

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

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

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

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

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

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

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

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

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

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

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

#include <stddef.h>

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

#endif
```

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

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

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

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

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

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

    return durum;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return 0;
}
```

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

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

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

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

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

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

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

## Özet

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

## Sonraki Adım

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