İçeriğe geç
academia.sh

Ders 10 / 15

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

Yayın Güncelleme

Bu derste

  • 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
İçindekiler

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.

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

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

// 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;
}
// 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.

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

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

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

Notlarım

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

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat