Ders 12 / 15
Kütüphane Üretimi
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.
Yayın Güncelleme
Bu derste
- Gömülü bağlamanın kütüphane değerini kopyaladığını, işaretçi tabanlı bağlamanın paylaştığını ölçer
- static ve extern kapsamın aynı adlı değişkeni neden ayrı ya da tek nesneye bağladığını gösterir
- Bu ikisini aynı ölçüm çerçevesinde kopya ve ortak sınıflarıyla ayırt eder
İçindekiler
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.
// 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
// 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.
// 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
// 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.
// 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;
}
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/externkarşıtlığı başlıkta kurulur: paylaşım isteniyorsa bildirim başlıktaexternolmalı ve tanım tek bir kaynak dosyada olmalı; birime özel tutmak isteniyorsa bildirim hiç başlığa taşınmadan kaynak dosyadastatickalmalıdır —staticher dahil eden birime ayrı bir kopya verir,externtek 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.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.