İçeriğe geç
academia.sh

Ders 02 / 13

Derleme Aşamaları

Dört aşamanın her biri farklı bir girdiye bakar: ön işlemci yalnız metne, derleyici tek bir birime, bağlayıcı birden çok birime, koşum ise yalnız veriye. Küçük bir ölçüm kesitiyle dördü yan yana koşturuluyor; ayrı bir çift program da aynı kaynağın bir satırının, kaynakta önceden verilmiş bir kararla iki farklı derlemeye nasıl girdiğini gösteriyor.

Yayın Güncelleme

Bu derste

  • Dört aşamanın her birinin karar verirken hangi girdiye baktığını ayırt eder
  • Küçük bir ölçüm kesitiyle dört sınıfı tek koşumda karşılaştırır
  • Aşamaların sırayla çalıştığını ve geri dönüşü olmadığını bir kaynak örneğiyle gösterir
İçindekiler

Önceki ders dört aşamayı adlandırdı: ön işlemci, derleyici, bağlayıcı, koşum; üç dosyalık çekirdeği tek koşumda çalıştırıp on dört gözlemi bu dört sınıfa dağıttı. Bu ders aynı dört adı, her birinin karar verirken hangi girdiye baktığı sorusuyla derinleştiriyor. Dört aşama aynı kaynağa bakar, ama hiçbiri aynı şeyi görmez — biri yalnız metin görür, biri tek bir dosya, biri birden çok dosyanın kesişimi, biri de yalnız o an elindeki veri. Bu fark soyut bir sınıflandırma değildir; her aşamanın elinde gerçekten o girdiden başka hiçbir şey yoktur ve bu ders bunu ölçerek gösterecek.

Dört Girdi

Her aşamanın kararını verirken elinde olan şey farklıdır:

Sınıf Baktığı girdi
ön işlemci Yalnız kaynağın metni; tip, sabit, birim kavramlarının hiçbiri henüz yok
derleyici Tek bir çeviri biriminin tamamı; o birimin dışına bakmaz
bağlayıcı Birden çok birimin ürettiği tanımlar; içeriklerini yorumlamaz, yalnız adları eşler
koşum Programın kendi verisi; kaynağın metni artık elde değildir

Bu sıralama aynı zamanda bir daralma sırasıdır. Ön işlemcinin elindeki bilgi en genişidir — bütün kaynak metnine erişebilir, ama içindeki hiçbir yapıyı anlamaz. Derleyici bu metnin yalnız bir biriminden sorumludur, ama artık tip ve ifade gibi yapıları görür. Bağlayıcı tek bir birimi bile görmez; yalnız birimlerin dışa açtığı adları görür. Koşum ise kaynağın hiçbir parçasını görmez, yalnız programın o an elindeki veriyi görür.

Bu daralma tek yönlüdür: bir aşama, bir öncekinin kaybettiği bilgiyi geri kazanamaz. Bir makro adı ön işlemcide metne dönüşür ve derleyici o adı hiç görmez — gördüğü yalnız yerine konmuş metindir, “bu bir makroydu” bilgisi kaynakta iz bırakmaz. Aynı şekilde bir değişkenin tipi derleyicide ikili bir düzene (nesne dosyasındaki bir sembole) indirgenir; bağlayıcı bu sembolü yalnız adıyla eşler, o adın hangi tipten bir değişkene ait olduğunu bilmez ve bilmesine gerek de yoktur — tip uyuşmazlığı varsa onu derleyici zaten kendi aşamasında yakalamıştır. Her aşama, kendinden öncekinin işini bitirdiğini varsayarak çalışır.

Bu güven zinciri kaynağın kendisiyle sınırlı değildir. Bağlayıcı, girdisi olan nesne dosyalarının doğru bir derleyiciden geldiğini de varsayar; herhangi bir aracın ürettiği, aynı biçimi taşıyan bir nesne dosyası aynı şekilde kabul edilir. Bu, dört aşamanın sırasının neden bir uygulamaya değil bir biçime bağlı olduğunu gösteriyor — her aşama kendinden önceki adımın hangi araçla yürütüldüğünü değil, ürettiği biçimin sözleşmeye uyup uymadığını denetler.

Dört Sınıf, Dört Satır

Aşağıdaki kesit önceki dersin çekirdeğinden dört satır alıyor — her sınıftan bir tane — ve yalnız bu dördünü yan yana koşturuyor. Bağlayıcı sınıfını göstermek için yine iki birim gerekiyor.

// asama.h — baska birimde tanimli tek adin bildirimi
#ifndef ASAMA_H
#define ASAMA_H

int kimlik(void);

#endif

asama.h kendi başına ne derlenir ne de bir birim sayılır — bir dosyayı bir birim yapan şey, onun cc komutuna ayrı bir kaynak olarak verilmesidir, #include ile başka bir dosyanın içine kopyalanması değil. Bu ders boyunca asama.h doğrulayıcıya yalnız KAYNAK olarak görünecek: içeriği ancak onu #include eden birimin metnine karıştıktan sonra derleyicinin önüne gelir. Başlık dosyasının bu paylaşım rolü sıradaki dersin konusu.

// secim.c — ikinci birim: tanim burada, cagri baglamada cozulur
#include "asama.h"

int kimlik(void) {
    return 7;
}
// asama.c — dort sinifi yan yana olcen kucuk kesit: her sinieftan tek satir
#include <stdio.h>
#include "asama.h"

#define KAT 3

static void gozle(const char *ad, const char *sinif, const char *kanit) {
    printf("%-30s%-14s%s\n", ad, sinif, kanit);
}

int main(void) {
    char tampon[64];

    printf("%-30s%-14s%s\n", "gozlem", "asama", "kanit");

    snprintf(tampon, sizeof tampon, "KAT metni %d olarak konuldu", KAT);
    gozle("makro degeri", "on islemci", tampon);

    int tam = 7 / 2;
    double kesirli = 7 / 2.0;
    snprintf(tampon, sizeof tampon, "tam %d kesirli %.1f", tam, kesirli);
    gozle("islecin tipe gore secimi", "derleyici", tampon);

    snprintf(tampon, sizeof tampon, "cagri %d verdi", kimlik());
    gozle("baska birimdeki islev", "baglayici", tampon);

    int veri[] = {4, 8, 15, 16};
    int toplam = 0;
    for (size_t i = 0; i < sizeof veri / sizeof veri[0]; i++) {
        toplam += veri[i];
    }
    const char *karar = (toplam > 40) ? "buyuk" : "kucuk";
    gozle("veriye bagli dallanma", "kosum", karar);

    return 0;
}
gozlem                        asama         kanit
makro degeri                  on islemci    KAT metni 3 olarak konuldu
islecin tipe gore secimi      derleyici     tam 3 kesirli 3.5
baska birimdeki islev         baglayici     cagri 7 verdi
veriye bagli dallanma         kosum         buyuk

DD4. Bu kesit çekirdeğin on dört satırından yalnız dördünü alıyor ve on_islemci, derleyici, baglayici, kosum sayaçlarını hiç taşımıyor — burada amaç dağılımı yeniden saymak değil, dört sınıfı tek bakışta yan yana koymak. Sayım kesin olarak yalnız önceki derste yapılır ve orada verilen 4/4/3/3 dağılımı burada değişmez.

Dört satırın her biri, bir önceki bölümdeki “baktığı girdi” tanımını doğruluyor. makro degeri satırı KATin sayısal değerini hiç görmeden, yalnız 3 metnini yerine koyarak üretildi — ön işlemci KATin bir tam sayı sabiti olduğunu bile bilmez. islecin tipe gore secimi satırı asama.cnin kendi içindeki iki değişkenin tipine bakarak üretildi — secim.cnin var olup olmadığının bir önemi yok. baska birimdeki islev satırı ise tam tersini gösteriyor: kimlikin gövdesi asama.cde hiç yok, yalnız bir bildirim var; kanıt ancak secim.c ile bağlanınca ortaya çıkıyor. veriye bagli dallanma satırı da kaynakta hiçbir sabit sayı taşımıyor; buyuk ya da kucuk yalnız veri dizisinin içeriğine bakılarak koşumda hesaplanıyor.

Dört satırın hangi sırayla üretildiği de aşamaların sırasını yansıtıyor. Program çalışmaya başladığında asama.c çoktan derlenmiş ve secim.c ile bağlanmıştır; koşum yalnız bu bitmiş programın içinde ilerler. gozle çağrılarının kaynaktaki sırası ile çıktının satır sırası aynı olsa da, bu bir zorunluluk değil bir gözlemdir: dört satırın hangi aşamada karar verildiği, çıktıda hangi sırada durduklarından bağımsızdır.

İki satır birbirine yakın durabilir ve yine de farklı sınıflara girebilir. Önceki dersin tam çekirdeğinde makro degeri ile numaralandirma sabiti ilk bakışta aynı işi yapıyor gibi görünür — ikisi de bir “sabit değer” üretir. Ama KAT bir #define olduğu için değeri kaynağa metin olarak konur ve ön işlemci bunu derleyici hiç devreye girmeden yapar; MAVI ise bir enum üyesi olduğu için değeri derleme zamanı sabit ifadesi olarak hesaplanır ve bu hesap asama.cnin kendi birim sınırları içinde kalır. İkisi de kaynakta sabit görünür, ikisi de koşumda değişmez, ama biri metin düzeyinde bitmiştir, öbürü derleyicinin tip ve ifade kurallarından geçer. Bu fark, “sabit görünen her şey aynı sınıfa girer” varsayımının neden yanlış olduğunu gösteriyor: sınıfı belirleyen görünüş değil, kaynaktaki yazım biçimidir.

Aşamaların Birbirine Kapalılığı

Bir aşamanın diğerine kapalı olması, aralarında bir iletişim kanalı olmadığı anlamına gelir. Derleyici, ön işlemcinin bir metni neden öyle ürettiğini sormaz; yalnız eline geleni kabul eder. Bağlayıcı, derleyicinin bir tipi nasıl kontrol ettiğini bilmez; yalnız üretilen sembolün adına bakar. Koşum ise kaynağın hangi satırdan geldiğini artık taşımaz; elindeki yalnız makine komutları ve verinin kendisidir.

Bu kapalılığın somut bir sonucu var: bir aşama, bir sonrakine yalnız kendi çıktısını bırakabilir, kendi süreciyle ilgili hiçbir ek bilgi taşıyamaz. Ön işlemci KATin bir makro olduğunu derleyiciye söylemez, çünkü söyleyecek bir kanalı yoktur — derleyicinin gördüğü yalnız 3 sayısıdır, sanki kaynakta hep öyle yazılmış gibi. Derleyici de kimlikin başka bir birimde tanımlı olduğunu bağlayıcıya “söylemez”; yalnız kendi ürettiği nesne dosyasına, çözülmemiş bir ad bırakır ve bağlayıcı bu adı kendi yöntemiyle arar. Her geçişte bir önceki aşamanın süreci kaybolur, yalnız sonucu kalır.

Bu, dört sınıfın neden birbirine karışmadığını da açıklıyor. Bir gözlemin hangi sınıfa girdiğini anlamak için “hangi aşama bu kararı verebilirdi” diye sormak yeterlidir: bir metin değişimiyse ön işlemci, bir tip veya sabit ifadeyse derleyici, birimler arası bir adsa bağlayıcı, veriye bakan bir koşulsa koşum. Bu ölçüt yalnız bu derste değil, kursun geri kalanında da aynı biçimde çalışacak.

Bağlayıcının “yalnız adlara bakması” da aynı kapalılığın bir sonucu. kimlik adı asama.c tarafında yalnız bir bildirim olarak durur, secim.c tarafında ise bir tanım olarak durur; bağlayıcının işi bu iki tarafı aynı ad üzerinden eşlemektir. Bunu yaparken kimlikin ne iş yaptığına, gövdesinin ne içerdiğine hiç bakmaz — yalnız adın her iki tarafta da var olup olmadığını ve varsa kaç kez tanımlandığını denetler. Bir bildirim ile bir tanımın nasıl ayrıldığı ve bağlayıcının bu ikisini nasıl bir araya getirdiği, sıradaki dersin konusu.

Koşum sınıfının kapalılığı ise en köktenidir. Program çalışmaya başladığında elinde kaynağın hiçbir izi kalmaz; veriye bagli dallanma satırının hangi kaynak satırından geldiğini bilen hiçbir mekanizma yoktur, çalışan program yalnız kendi verisine ve o verinin ürettiği sonuçlara bakar. Bu yüzden bir koşum kararını “geriye izleyip” kaynaktaki karşılığını bulmak, öteki üç aşamadaki gibi doğrudan değildir — kaynakla koşum arasındaki bağ, derleme ve bağlama sırasında geri dönüşsüz biçimde bir kere kurulmuştur.

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

Aşamalar sırayla çalışır ve geri dönmez: ön işlemcinin ürettiği metin yanlışsa derleyici onu düzeltmez, yalnız sonucunu görür. Bunu göstermenin en doğrudan yolu, aynı koşullu gövdeyi iki kez derlemektir — aradaki tek fark kaynağa eklenen bir satır.

Kaynağın farklı gövdeler arasında #ifdef ile seçim yapması yaygın bir örüntüdür ve seçim her zaman ön işlemcide, derleme daha başlamadan karara bağlanır. Önceki dersteki KAT makrosu ve BIRLESTIR ad birleştirmesi de aynı sınıftandı: ikisi de derleyici kaynağı görmeden önce, metin düzeyinde sonuca ulaşmıştı. Aradaki fark şu — KAT her zaman aynı metne açılıyordu, ama AYRINTILI tanımlı olup olmaması kaynağın hangi dalının derlemeye gireceğini belirliyor. Bu, tek bir makro değerinden daha güçlü bir örnek: burada seçilen şey bir sayı değil, kaynağın kendisinin bir parçası.

// gorunum.c — govde secimi kaynakta bir #define ile onceden karara baglanir
#include <stdio.h>

/* AYRINTILI burada tanimli degil: on islemci kisa govdeyi derlemeye sokar. */

int main(void) {
    printf("kosullu govde: ");
#ifdef AYRINTILI
    printf("ayrintili govde derlemeye girdi\n");
#else
    printf("kisa govde derlemeye girdi\n");
#endif
    return 0;
}
kosullu govde: kisa govde derlemeye girdi
// gorunum.c — ayni kosullu blok, govde secimini degistiren tek satir eklendi
#include <stdio.h>

#define AYRINTILI

int main(void) {
    printf("kosullu govde: ");
#ifdef AYRINTILI
    printf("ayrintili govde derlemeye girdi\n");
#else
    printf("kisa govde derlemeye girdi\n");
#endif
    return 0;
}
kosullu govde: ayrintili govde derlemeye girdi

DD5. İki dosya da aynı adı taşıyor (gorunum.c) ve ikinci blok birinciyi diskte değiştiriyor; bu, iki ayrı derleme olduğunu, aynı programın iki farklı çalışması olmadığını göstermek içindir. Aradaki tek fark, ikinci dosyanın başına eklenen tek bir #define satırıdır — gövdenin geri kalanı harfi harfine aynıdır.

#ifdef burada koşullu derleme (conditional compilation) ailesinin tek bir üyesidir; aynı ailede bir adın tanımlı olmadığını sınayan #ifndef (asama.h’nin çifte içermeyi engellemek için kullandığı yönerge de budur), bir ifadenin değerini sınayan #if, başka bir dalı ekleyen #elif ve hiçbirinin doğru olmadığı durumu karşılayan #else bulunur. Hepsi aynı sınıfa girer, çünkü hepsi derleyici devreye girmeden, yalnız ön işlemcinin elindeki tanımlı adlara bakarak karar verir.

İki derlemenin de kaynağı, satır satır bakıldığında, #ifdef bloğunun iki dalını da taşıyor. Ama derleyici hiçbir zaman iki dalı da görmez: ön işlemci, AYRINTILI tanımlı mı diye bakar ve seçilmeyen dalı kaynaktan tümüyle siler — derleyiciye giden metinde o dal artık yoktur, yorumlanmamış ya da atlanmış değil, hiç yazılmamış gibidir. Derleyici bu yüzden “iki seçenekten birini eledi” demez; onun gördüğü kaynakta zaten tek bir gövde vardır. Bir sonraki aşamaya (derleyiciye) geçildiğinde ön işlemcinin verdiği karar artık sorgulanamaz; tek yol kaynağı değiştirip yeniden en baştan başlamaktır — iki blok arasındaki fark da tam olarak budur.

Bu, aşamaların neden bir zincir değil bir tek yönlü sıra olduğunu gösteriyor. Bir zincirde her halka bir öncekini denetleyebilir; burada hiçbir aşama bir öncekinin verdiği kararı geri alamaz, yalnız onun sonucunu devralır. Derleme Aşamaları dersinin adı da buradan geliyor: aşama, geri dönülebilen bir adım değil, ardışık ve tek yönlü bir sıradır.

Bu tek yönlülüğün pratik bir sonucu var: bir programın verdiği yanlış sonucu ararken, önce o sonucun hangi aşamada karara bağlandığını sormak aramayı daraltır. kosullu govde satırının yanlış çıktığı görülürse aranacak yer derleyici ya da bağlayıcı değil, kaynaktaki #define satırıdır — çünkü derleyicinin önüne gelen metin zaten tek bir gövdeye indirgenmiştir ve derleyici o metni doğru yorumlamaktan başka bir şey yapmamıştır. Aynı mantık öteki üç sınıf için de geçerlidir: bir sınıfın ürettiği sonucun ardında yalnız o sınıfın baktığı girdi vardır, öncesi değil.

Özet

  • Dört aşama aynı kaynağa bakar ama farklı girdiler görür: ön işlemci metin, derleyici tek birim, bağlayıcı birden çok birimin tanımları, koşum ise yalnız veri.
  • Küçük bir kesit dört sınıftan birer satırı yan yana koşturdu; her satır kendi sınıfının baktığı girdiyi doğruladı.
  • Aşamalar sırayla çalışır ve geri dönmez: ön işlemcinin elediği bir dal derleyiciye hiç ulaşmaz, derleyici onu “eledi” olarak değil, hiç yokmuş gibi görür.
  • Bir aşamanın kararını değiştirmenin tek yolu kaynağı değiştirip zinciri en baştan koşturmaktır; hiçbir aşama bir sonrakinden geri dönüş bilgisi almaz.
  • Bir hatanın hangi aşamaya ait olduğunu bilmek aramayı daraltır: sonucun ardında yalnız o aşamanın baktığı girdi vardır, öncesinde olan biten değil.

Sonraki Adım

Bağlayıcı sınıfı bu derste yalnız bir çağrıyla gösterildi: kimlikin bildirimi bir dosyada, tanımı başka bir dosyadaydı ve asama.h bu bildirimi ikisi arasında taşıyan araç oldu. Sıradaki ders bu paylaşımın nasıl kurulduğuna bakıyor — bir başlık dosyasının bir bildirimi birden çok birime nasıl taşıdığı, bildirim ile tanımın neden ayrı tutulması gerektiği ve bunların birbirine karıştırılmasının nereye kadar zararsız kaldığı.

İ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