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.