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
(
NULLkalması 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ı dersindekigoto temizledeyiminin ü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.
atexitile kaydedilen işleyicilerexitçağrıldığında kayıt sırasının tersiyle çalışıyor;_exitise 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.