---
title: 'Kaynak Temizleme Deyimleri'
source: 'https://academia.sh/tr/kurslar/c-sistem-programlama/kaynak-temizleme-deyimleri'
course: 'C ile Sistem Programlama'
language: tr
updated: '2026-09-26T03:02:44+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Birden fazla kaynağın açık sırasının tersiyle kapatılması gereken bir işlevi kurar'
    - 'Bir hata yolunda yarı açık kalan kaynağın hiçbir okuyucuya ulaşmadan kapandığını ölçer'
    - 'Kaynağı serbest bırakan kod ile kaynağı gören kod arasındaki farkı adlandırır'
---

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