İçeriğe geç
academia.sh

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/extern karşıtlığı başlıkta kurulur: paylaşım isteniyorsa bildirim başlıkta extern olmalı ve tanım tek bir kaynak dosyada olmalı; birime özel tutmak isteniyorsa bildirim hiç başlığa taşınmadan kaynak dosyada static kalmalıdır — static her dahil eden birime ayrı bir kopya verir, extern tek 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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat