İçeriğe geç
academia.sh

Ders 12 / 13

Yapılar, Birlikler ve Numaralandırmalar

Yapı alanlarının derlemede yerleşmesi, bir birliğin aynı belleği paylaşması ve numaralandırma sabitlerinin derleme zamanı sabiti olması; birlikte hangi alanın geçerli olduğunu dilin izlemediğinin bir etiket alanıyla gösterilmesi.

Yayın Güncelleme

Bu derste

  • Yapı alanlarının derlemede yerleştiğini ve atamanın bütün alanları kopyaladığını ölçer
  • Bir birliğin aynı belleği paylaştığını etiketli okumayla güvenle gösterir
  • Numaralandırma sabitlerinin derleme zamanı sabiti olduğunu örnekle doğrular
İçindekiler

Diziler ve Dizgiler dersi, aynı tipten değerlerin bir dizi içinde nasıl sıralandığını ölçtü. Bu ders farklı tiplerden değerlerin tek bir ad altında nasıl toplandığını ölçüyor: bir yapı farklı tipleri yan yana yerleştirir, bir birlik aynı belleği farklı yorumlarla paylaştırır, bir numaralandırma ise adlandırılmış sabitler üretir — üçü de kaynakta yazılan bildirimden, derlemede karara bağlanan düzenekler.

Programlama Temelleri kursu (M01/K02) kayıt benzeri veri gruplamasını Python’la zaten kurmuştu; orada alanlar bir sözlük ya da nesne içinde, çalışma zamanında eklenip çıkarılabilen adlardı. C’nin karşılığı burada ölçülüyor: bir yapının hangi alanları taşıyacağı, bu alanların bellekte hangi sırayla duracağı ve bir numaralandırma sabitinin değeri — hepsi kaynakta yazılan bildirimden, program hiç çalışmadan derlemede sabitleniyor. Bu, kursun ölçü ekseninin son durağı: bir kaynağın dört aşamadan geçerek programa dönüştüğü C’nin Konumu dersinde açılan soru, burada üç yeni düzeneğin hangi aşamada karara bağlandığını sorarak kapanıyor.

Yapı: Alanların Derlemede Yerleşmesi

Bir yapı bildirimi, her alanın adını ve tipini kaynakta sabitler; bu bildirimden sonra o yapı tipindeki her değişken aynı alan düzenini taşır. Diziler ve Dizgiler dersinde ölçülen bir dizi, aynı tipten değerleri sıralarken, bir yapı farklı tiplerden değerleri tek bir ad altında toplar — bir noktanın x ve y koordinatları gibi, birlikte anlam taşıyan ama farklı olabilecek tiplerden değerler. Bir yapı değişkenini bir başkasına atamak, bütün alanları tek seferde kopyalar.

// yapi.c — yapinin alanlari derlemede yerlesir, atama butun alanlari kopyalar
#include <stdio.h>

struct Nokta {
    int x;
    int y;
};

int main(void) {
    struct Nokta a = {3, 4};
    struct Nokta b = a;
    b.x = 99;

    printf("%s %d\n", "a.x", a.x);
    printf("%s %d\n", "a.y", a.y);
    printf("%s %d\n", "b.x", b.x);
    printf("%s %d\n", "b.y", b.y);
    return 0;
}
a.x 3
a.y 4
b.x 99
b.y 4

TS12. struct Nokta bildirimi, her Nokta değişkeninin bir x ve bir y alanı taşıyacağını, bu alanların adlarını ve tiplerini derlemede sabitliyor — bu yapı hangi alanları taşıyacağını koşumda değil, kaynakta belirliyor. b = a ataması, anın her iki alanını da bye kopyalıyor; ardından b.xe yapılan değişiklik yalnız bnin kendi kopyasını etkiliyor, a.x hâlâ 3. Bu, önceki derste ölçülen parametre kopyalama kuralının bir yapı için nasıl işlediğini gösteriyor: bir yapı değişkeni bir başkasına atandığında ya da bir işleve geçirildiğinde, aktarılan şey bütün alanların bir kopyasıdır — dizinin aksine, yapı işleve geçtiğinde ilk elemanının adresine dönüşmez, tüm alanlarıyla kopyalanır.

Alanların bellekteki sırası da kaynaktaki bildirim sırasıyla belirlenir: struct Noktanın x alanı her zaman yden önce yerleşir, çünkü bildirimde önce yazılan alan önce gelir. Bir yapı, kendi içine başka bir yapıyı da alan bulundurabilir — struct Nokta alanları taşıyan bir struct Cizgi { struct Nokta bas; struct Nokta son; }; gibi — ve bu iç içe geçme de aynı kuralı katmanlı biçimde sürdürür: her iç yapının kendi alanları, dış yapının kendi alanı içinde, yine bildirim sırasına göre yerleşir. Bu yerleşim kararı, tıpkı bir dizinin eleman sayısı gibi, program hiç çalışmadan derleyici tarafından sabitlenir — hangi alanın hangi adreste durduğu koşumdaki veriye hiç bakılmadan, yalnız bildirimin kendisinden belirlenir. Bu kursta hiçbir gerçek adres ya da bayt uzaklığı basılmıyor, ama bildirim sırasının bir sonucu olması, yapı tanımının neden hem verinin anlamını hem bellekteki düzenini aynı anda belirlediğini gösteriyor.

Birlik: Aynı Belleğin Paylaşılması

Bir birlik, birden çok alanı aynı bellek alanında tanımlar — her alan aynı adrese karşılık gelir, farklı bir tip yorumuyla. Birliğin kendisi hangi alanın “geçerli” olduğunu izlemez; bu bilgi, bir etiket alanıyla ayrıca taşınmalıdır.

// birlik.c — birligin ayni alani paylasmasi, etiketli okumayla guvenli kullanilir
#include <stdio.h>

enum Tur { TAM_SAYI, ONDALIK };

struct EtiketliDeger {
    enum Tur tur;
    union {
        int tam;
        float ondalikli;
    } deger;
};

static struct EtiketliDeger tam_sayi_uret(int v) {
    struct EtiketliDeger e;
    e.tur = TAM_SAYI;
    e.deger.tam = v;
    return e;
}

static struct EtiketliDeger ondalik_uret(float v) {
    struct EtiketliDeger e;
    e.tur = ONDALIK;
    e.deger.ondalikli = v;
    return e;
}

static void yazdir(struct EtiketliDeger e) {
    if (e.tur == TAM_SAYI) {
        printf("%s %d\n", "tam sayi olarak okundu", e.deger.tam);
    } else {
        printf("%s %.2f\n", "ondalik olarak okundu", e.deger.ondalikli);
    }
}

int main(void) {
    struct EtiketliDeger bir = tam_sayi_uret(42);
    struct EtiketliDeger iki = ondalik_uret(3.5f);
    yazdir(bir);
    yazdir(iki);
    return 0;
}
tam sayi olarak okundu 42
ondalik olarak okundu 3.50

TS13. union içindeki tam ile ondalikli, aynı bellek alanını paylaşıyor — biri yazıldığında öbürünün eski değeri anlamsız hâle geliyor, çünkü ikisi aynı baytları farklı bir tip merceğinden okuyor. tam_sayi_uret yalnız deger.tamı yazıyor, ondalik_uret yalnız deger.ondalikliyi; yazdir de her zaman tur alanına bakıp hangi yorumun geçerli olduğunu ondan öğreniyor ve yalnız o alanı okuyor. Bu disiplin sayesinde ders hiçbir zaman “yazılmayan” alanı okumuyor — bir için hep tam, iki için hep ondalikli okunuyor. union EtiketliDeger (deger alanı) kendi başına hangi üyenin son yazılan olduğunu bilmez; bu bilgiyi taşımak tur alanının ve o alanı doğru tutmak kaynak yazarının işi.

Bir birliğin kaplayacağı yer de derlemede sabitlenir: standardın koyduğu ölçü, birliğin en büyük alanını taşımaya yetecek kadar yer ayrılmasıdır — çünkü herhangi bir anda herhangi bir alan yazılabilir olmalı. Bu kursta hiçbir gerçek boyut basılmadığı için bu ders bunu ölçmüyor, ama iki bildirimin büyüme biçimi arasındaki farkı belirtmek gerekir: yapı alanları arka arkaya dizer, birlik üst üste bindirir. Bu yüzden birliğe en büyüğünden küçük bir alan eklemek toplam yeri genellikle büyütmez; yapıya eklemek ise alan mevcut dolgunun içine sığmadıkça büyütür. İkisinde de son sözü hizalama söyler: standart hem yapının hem birliğin sonunda adsız dolgu bulunabileceğini belirtir, bu yüzden toplam yer alanların boyutlarından doğrudan okunmaz.

Numaralandırma: Derleme Zamanı Sabiti

Bir numaralandırma bildirimi, adlandırılmış tam sayı sabitleri üretir; bu sabitler kaynakta yazılan sabit ifadelerin kullanılabileceği her yerde kullanılabilir — örneğin bir dizinin uzunluğunu belirlemek için.

// asama.c — numaralandirma sabiti (kesit), derleme zamani sabiti olarak kullanilir
#include <stdio.h>

enum Renk { KIRMIZI = 1, YESIL = 2, MAVI = 4 };

int main(void) {
    printf("%s %d\n", "numaralandirma sabiti", (int)MAVI);

    int renkler[MAVI];
    printf("%s %d\n", "sabitle olusturulan dizi eleman sayisi",
           (int)(sizeof renkler / sizeof renkler[0]));
    return 0;
}
numaralandirma sabiti 4
sabitle olusturulan dizi eleman sayisi 4

MAVI değeri, enum Renk bildiriminde yazılan 4 sabitinden geliyor ve bu değer program hiç çalışmadan bellidir. int renkler[MAVI]; bildirimi bunu doğruluyor: MAVI, Diziler ve Dizgiler dersinde ölçülen sabit ifade katlamasının kabul ettiği türden bir ifade — bir değişken ya da bir işlev çağrısı olsaydı, dizinin boyutu derlemede belirlenemezdi. renkler dizisinin eleman sayısı, hiçbir gerçek boyut basılmadan, yine sizeof oranıyla ölçülüyor ve 4 çıkıyor — MAVI ile birebir aynı sayı, çünkü ikisi de aynı derleme zamanı sabitinden türüyor.

enum Renk bildirimindeki KIRMIZI = 1, YESIL = 2, MAVI = 4 gibi açık değerler zorunlu değildir; bir numaralandırma, hiçbir değer yazılmadan da bildirilebilir ve bu durumda ilk ad 0dan başlayarak, her ad bir öncekinden bir fazla değer alır. Buradaki bildirim değerleri açıkça yazıyor, çünkü KIRMIZI, YESIL ve MAVI ayrı bit konumlarını (1, 2, 4) temsil ediyor ve bu, renklerin bir bit maskesiyle birleştirilebilmesini sağlıyor — KIRMIZI | MAVI gibi bir ifade, iki rengi aynı anda temsil eden 5 değerini üretebilir. Bu kullanım da yine derlemede sabitlenen bir özellik: hangi sabitin hangi bit konumuna karşılık geldiği, kaynakta yazılan = işaretinden geliyor, koşumdaki hiçbir veriye bakılmıyor.

Sınırlayıcı Ölçüm

Bir birlik, hangi alanının en son yazıldığını kendi başına izlemez — bu bilgiyi dil değil, kaynak yazarı taşımak zorundadır. Yukarıdaki EtiketliDeger yapısı bunu bir etiket alanıyla çözüyor: tur, degerin hangi üyesinin geçerli olduğunu söylüyor ve yazdir her zaman önce ture bakıyor.

Eğer bu disiplin bırakılsaydı — yazdir ture hiç bakmadan, örneğin her zaman deger.ondalikliyi okusaydı — bir değişkeni için (ki tam_sayi_uret yalnız deger.tamı yazmıştı) okunan bayt örüntüsü bir int olarak yazılmış, bir float olarak okunmuş olurdu. Bu, birliğin garanti ettiği tek şeyin aynı adresi paylaşmak olduğunu, hangi tip yorumunun anlamlı olduğunu garanti etmediğini gösteriyor — yorum uyuşmazlığının sonucu belirsiz bir bit örüntüsüdür ve bu kursun genel kuralınca kod bloğunda denenmiyor. Yanına konan tanımlı karşılık, yukarıdaki etiketli okumanın kendisi: her okuma, o birliğe en son hangi üyenin yazıldığını bağımsız bir alandan (tur) öğrenip yalnız o üyeyi okuyor ve bu ölçüm üç kez koşturulduğunda da aynı iki satırı üretiyor.

Etiketli okumanın çalışması için, etiketin her zaman ilgili yazmayla birlikte güncellenmesi gerekiyor — tam_sayi_uret ve ondalik_uret işlevlerinin ikisi de, kendi alanlarını yazmadan önce turü ayarlıyor. Eğer bir üçüncü işlev deger.tamı yazıp turü güncellemeyi unutsaydı, o değişkeni okuyan yazdir hâlâ eski tur değerine bakar ve yanlış üyeyi okurdu — birlik kendi başına bu tutarsızlığı ne üretebilir ne engelleyebilir, çünkü birliğin kendisi yalnız bellek paylaşımını sağlıyor, etiketle senkron kalmayı değil. Bu, C’nin birlik düzeneğinin bilinçli bir tasarım kararını yansıtıyor: dil, alan seçimini izlemenin maliyetini üstlenmiyor — her yazmada hangi alanın aktif olduğunu ayrıca kaydetmiyor — ve bu sorumluluğu, tıpkı dizi uzunluğu bilgisinde olduğu gibi, tamamen kaynak yazarına bırakıyor.

Bu, yapı ile birliğin birbirini tamamlayan iki düzenek olduğunu da gösteriyor: yapı her alan için ayrı bellek ayırıp hepsini aynı anda geçerli kılarken, birlik tek bir bellek alanını paylaştırıp yalnız birini geçerli kılıyor. EtiketliDeger, bu ikisini birleştiriyor — dıştaki yapı hem etiketi hem paylaşılan alanı taşıyor ve etiket alanı sayesinde birliğin izlemediği bilgi yapı düzeyinde tutulmuş oluyor.

Bu birleşim, C’de “etiketli birlik” (tagged union) diye anılan yaygın bir kalıbın temelini oluşturuyor: birden çok olası biçimden yalnız birini taşıyan bir değeri modellemek isteyen bir kaynak yazarı, bu üç düzeneği — yapı, birlik ve numaralandırma — birlikte kullanır. Numaralandırma, hangi biçimin geçerli olduğunu adlandırılmış sabitlerle ifade eder; birlik, o biçimlerin ortak bir bellek alanını paylaşmasını sağlar; yapı, ikisini bir arada, tek bir değer olarak taşır. Bu derste ölçülen üç düzenek, bu yüzden birbirinden bağımsız üç konu değil, aynı kalıbın üç bileşeni.

Özet

  • Bir yapının alanları, adları ve tipleriyle birlikte derlemede sabitlenir; bir yapı değişkeni bir başkasına atandığında bütün alanlar tek seferde kopyalanır.
  • Bir birlik, birden çok alanı aynı bellek adresinde tanımlar; hangi alanın son yazılan olduğunu dil izlemez, bu bilgiyi kaynak yazarı ayrıca (bir etiket alanıyla) taşımak zorundadır.
  • Numaralandırma sabitleri (enum Renk içindeki MAVI gibi) derleme zamanı sabitleridir; bir dizinin uzunluğunu belirlemek gibi sabit ifade gerektiren her yerde kullanılabilirler — sınıf derleyici.
  • Bir birlikte yazılmayan bir üyeyi okumak (etiketsiz, yanlış tip varsayımıyla) baytları başka bir tip olarak yeniden yorumlar; C bunu yasaklamaz ama son yazılan üyeye karşılık gelmeyen baytların değeri belirtilmemiştir ve okunan örüntü bir tuzak temsiliyse davranış tanımsız olur. Bu kursta koşturulmaz; tanımlı karşılık, etiketli okumadır.
  • Yapı her alanı ayrı bellekte tutup hepsini birden geçerli kılarken, birlik tek bir bellek alanını paylaştırıp yalnız birini geçerli kılar — ikisi birbirini tamamlayan iki düzenek.
  • Numaralandırma, birlik ve yapı bir arada kullanıldığında “etiketli birlik” kalıbını kurar: numaralandırma hangi biçimin geçerli olduğunu adlandırır, birlik biçimlerin belleğini paylaştırır, yapı ikisini tek bir değerde birleştirir.

Sonraki Adım

Bu konuda ölçülen her karar, bir programın tek bir koşumu içinde kaldı: bir tipin aralığı, bir adın kapsamı, bir işlecin seçimi, bir dalın izlediği yol, bir çağrının bağlandığı gövde, bir dizinin uzunluğu, bir birliğin geçerli alanı — hepsi ya kaynaktan ya da o koşumun kendi verisinden okunabiliyordu. Girdi/Çıktı dersi bu zemine son bir soru ekliyor: bir programın dışarıdan aldığı ya da dışarıya yazdığı veri, hangi aşamada yorumlanıyor? Bir biçim dizesindeki (format string) her yer tutucu, karşılık gelen bağımsız değişkenin tipiyle uyuşmak zorunda, ama bu uyuşmayı derleyici yalnız belirli koşullarda yakalayabiliyor — dilin kendisi bunu garanti etmiyor. Bu derste ölçülen EtiketliDeger örneğinde etiketi doğru tutmak kaynak yazarının işiydi; sıradaki derste biçim dizesini argümanlarla uyumlu tutmak da aynı şekilde kaynak yazarının sorumluluğunda olacak.

Bu konuda ölçülen yedi ders, dört sınıfın (ön işlemci, derleyici, bağlayıcı, koşum) her birine en az bir kez uğradı: Veri Tipleri ve İşleçler derleyicide, Denetim Akışı koşumda, Değişkenler ile Fonksiyonlar bağlayıcıda, Diziler ve Dizgiler ikisinde birden ve bu ders yine derleyicide durdu. Kapanış dersi, bu yedi dersin ölçtüğü kararları tek bir tabloda topluyor ve girdi çıktının kendi payını — biçim dizelerinin koşumda yorumlanmasını — bu tabloya ekliyor.

İ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