Ders 01 / 13
C'nin Konumu
C'de bir kaynağın çalışan bir programa dönüşmesi tek adım değildir: dört aşamadan geçer ve her aşama ayrı bir soruya bakar. Üç dosyalık bir ölçüm çekirdeği bu dört aşamayı tek koşumda gösterir ve on dört gözlemin 4'ünü ön işlemciye, 4'ünü derleyiciye, 3'ünü bağlayıcıya, 3'ünü koşuma yazar.
Yayın Güncelleme
Bu derste
- Dört derleme aşamasını (ön işlemci, derleyici, bağlayıcı, koşum) tanımlar ve ayırt eder
- Üç dosyalık ölçüm çekirdeğini koşturarak on dört gözlemin aşamalara dağılımını doğrular
- Aynı türden bir kararın kaynakta hangi aşamada verildiğini örnekler üzerinden gösterir
İçindekiler
Rust müfredatı beş kursla kapandı ve son kursu tek bir soru sordu: derleyicinin verdiği güvence nerede bitiyor (M10/K05). C’de bu sorudan önce gelen bir soru var. Bir kaynak dosya çalışan bir programa dönüşene kadar kaç kez el değiştiriyor? Yanıt tek bir adım değil, dörttür — ve bu kurs boyunca sorulacak tek soru, bir kararın hangi elde verildiğidir.
Rust’ta bu tür bir soru tek bir yerde sorulmuştu: kaynakta yazılan bir satır bir değer mi üretiyor (M10/K01). C’de aynı tür soru dört kez sorulur, çünkü kaynak programa dönüşene kadar dört ayrı eli geçiyor ve her el kendi kararını kendi bildiği şeye bakarak veriyor. Bu ders o dört eli adlandırıyor ve aralarındaki farkı tek bir programı derleyip çalıştırarak gösteriyor.
Dört Aşama
Bir C kaynağı programa dönüşürken sırayla dört elden geçer. Kurs boyunca bu dört ad değişmez:
| Sınıf | Tanım |
|---|---|
| ön işlemci (preprocessor) | Derleyici kaynağı görmeden önce, metin düzeyinde karara bağlanır |
| derleyici (compiler) | Tek bir birim çevrilirken, tiplerden ve sabitlerden karara bağlanır |
| bağlayıcı (linker) | Birimler bir araya gelirken, hangi tanımın kullanılacağı belli olur |
| koşum (runtime) | Ancak program çalışırken, veriye bakılarak belli olur |
Bu dört adın ortak noktası, hepsinin aynı soruya farklı bir yanıt vermesidir: “bu kararı vermek için hangi bilgiye ihtiyaç var?” Ön işlemci yalnız metne bakar, tip diye bir şey bilmez. Derleyici tek bir çeviri birimini (translation unit) görür, ama o birimin dışındaki hiçbir şeyi bilmez. Bağlayıcı birden çok birimi bir araya getirir, ama hiçbirinin içeriğini yorumlamaz — yalnız adları eşler. Koşum ise yalnız veriye bakar; kaynağın kendisi artık elinde bile değildir.
Bu ölçü, M08/K05’teki “bir tipin hangi sınıf yolunda bulunması gerektiği” sorusuyla ve M09/K05’teki “bir bilginin kaynağının ne olduğu” (kaynak, araç, koşum) sorusuyla karışmaz. Orada ölçülen bir bağımlılığın nerede arandığı ya da bir bilginin kimden geldiğiydi; burada ölçülen bir kararın hangi aşamada verildiği ve aşama sayısının dört olduğudur.
Aşamalar Neden Görünür
Bu dört aşamanın görünür olması dilin donanıma yakınlığının bir sonucudur. C, kaynaktan çalışan bir ikiliye giden yolun hiçbir adımını programcıdan gizlemez: ön işlemcinin ürettiği metin okunabilir bir ara çıktıdır, derleyicinin ürettiği nesne dosyası incelenebilir, bağlayıcının hangi tanımı seçtiği izlenebilir. Bu görünürlük bir eksiklik değil, dilin kendi tasarım kararıdır — ve kursun geri kalanında tekrar tekrar karşımıza çıkacaktır.
C standardı (C17) kaynaktan ikiliye giden yolu sekiz çeviri aşamasına (translation phase) ayırır. Bu kurs o sekiz aşamayı üç sınıfta toplar — ön işlemci, derleyici, bağlayıcı — ve üstüne dördüncü sınıf olarak koşumu ekler; koşum standardın çeviri aşamalarına dahil değildir, ayrı bir maddede yürütme ortamı olarak tanımlanır. Programcının kendi verdiği kararlar bu dört noktada toplanır. Standardın sekiz aşaması arasındaki ayrımlar — örneğin metin kodlamasının çözülmesiyle satır sonlarının birleştirilmesi ayrı aşamalardır — bu kursun ölçtüğü kararları etkilemez; onlar için tek isim yeter: ön işlemci.
Ölçüm Çekirdeği
Kursun geri kalanında kullanılacak çekirdek üç dosyadır. Bağlayıcı sınıfını ölçmek birden fazla birim gerektirdiği için çekirdek baştan çok birimlidir: bir başlık dosyası, iki kaynak dosyası. İlk dosya yalnız bildirim taşır — hiçbir tanım içermez.
// asama.h — baska birimlerde tanimli adlarin bildirimi #ifndef ASAMA_H #define ASAMA_H /* Bu iki islev baska bir birimde tanimli: cagrilari baglama asamasinda cozulur. */ int kimlik(void); const char *birim_adi(void); /* Bu degisken de baska bir birimde tanimli. */ extern int birim_sayaci; #endif
İkinci dosya bu bildirimlerin karşılığını taşıyor. secim.c kendi başına derlenebilir bir
birimdir ve asama.c’nin hiçbir satırını bilmez.
// secim.c — ikinci birim: tanimlar burada, cagrilar baglamada cozulur
#include "asama.h"
int birim_sayaci = 2;
int kimlik(void) {
return 7;
}
const char *birim_adi(void) {
return "secim";
}
Üçüncü dosya asıl ölçümü taşıyor. On dört ayrı gözlemi bir tabloya yazıyor, her satırın hangi aşamada karara bağlandığını da yanına ekliyor.
// asama.c — bir kararin hangi asamada verildigini olcen cekirdek
#include <stdio.h>
#include <string.h>
#include "asama.h"
#define KAT 3
#define BIRLESTIR(a, b) a##b
enum Renk { KIRMIZI = 1, YESIL = 2, MAVI = 4 };
static int BIRLESTIR(topla, _uc)(int x) {
return x + 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];
int on_islemci = 0, derleyici = 0, baglayici = 0, kosum = 0;
printf("%-30s%-14s%s\n", "gozlem", "asama", "kanit");
/* --- on islemci --- */
snprintf(tampon, sizeof tampon, "KAT metni %d olarak konuldu", KAT);
gozle("makro degeri", "on islemci", tampon);
on_islemci++;
#ifdef AYRINTILI
gozle("kosullu govde", "on islemci", "ayrintili govde derlemeye girdi");
#else
gozle("kosullu govde", "on islemci", "kisa govde derlemeye girdi");
#endif
on_islemci++;
snprintf(tampon, sizeof tampon, "uretilen ad %d verdi", BIRLESTIR(topla, _uc)(4));
gozle("ad birlestirme", "on islemci", tampon);
on_islemci++;
gozle("baslik metni", "on islemci", "bildirimler kaynaga metin olarak kopyalandi");
on_islemci++;
/* --- derleyici --- */
snprintf(tampon, sizeof tampon, "sizeof(char) == %d", (int)sizeof(char));
gozle("tip boyutu birimi", "derleyici", tampon);
derleyici++;
int sabit_dizi[2 * 3 + 1];
snprintf(tampon, sizeof tampon, "eleman sayisi %d", (int)(sizeof sabit_dizi / sizeof sabit_dizi[0]));
gozle("sabit ifade katlamasi", "derleyici", tampon);
derleyici++;
snprintf(tampon, sizeof tampon, "MAVI sabiti %d", (int)MAVI);
gozle("numaralandirma sabiti", "derleyici", tampon);
derleyici++;
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);
derleyici++;
/* --- baglayici --- */
snprintf(tampon, sizeof tampon, "cagri %d verdi", kimlik());
gozle("baska birimdeki islev", "baglayici", tampon);
baglayici++;
snprintf(tampon, sizeof tampon, "birim %s olarak bildirildi", birim_adi());
gozle("baska birimdeki ad", "baglayici", tampon);
baglayici++;
snprintf(tampon, sizeof tampon, "sayac %d", birim_sayaci);
gozle("baska birimdeki degisken", "baglayici", tampon);
baglayici++;
/* --- kosum --- */
int veri[] = {4, 8, 15, 16};
int toplam = 0;
for (size_t i = 0; i < sizeof veri / sizeof veri[0]; i++) {
toplam += veri[i];
}
snprintf(tampon, sizeof tampon, "toplam %d", toplam);
gozle("veriden hesaplanan deger", "kosum", tampon);
kosum++;
const char *metin = "merhaba dunya";
snprintf(tampon, sizeof tampon, "uzunluk %d", (int)strlen(metin));
gozle("dizgi uzunlugu", "kosum", tampon);
kosum++;
const char *karar = (toplam > 40) ? "buyuk" : "kucuk";
gozle("veriye bagli dallanma", "kosum", karar);
kosum++;
printf("\n%-14s%d\n", "on islemci", on_islemci);
printf("%-14s%d\n", "derleyici", derleyici);
printf("%-14s%d\n", "baglayici", baglayici);
printf("%-14s%d\n", "kosum", kosum);
printf("toplam gozlem: %d\n", on_islemci + derleyici + baglayici + kosum);
return 0;
}
gozlem asama kanit makro degeri on islemci KAT metni 3 olarak konuldu kosullu govde on islemci kisa govde derlemeye girdi ad birlestirme on islemci uretilen ad 7 verdi baslik metni on islemci bildirimler kaynaga metin olarak kopyalandi tip boyutu birimi derleyici sizeof(char) == 1 sabit ifade katlamasi derleyici eleman sayisi 7 numaralandirma sabiti derleyici MAVI sabiti 4 islecin tipe gore secimi derleyici tam 3 kesirli 3.5 baska birimdeki islev baglayici cagri 7 verdi baska birimdeki ad baglayici birim secim olarak bildirildi baska birimdeki degisken baglayici sayac 2 veriden hesaplanan deger kosum toplam 43 dizgi uzunlugu kosum uzunluk 13 veriye bagli dallanma kosum buyuk on islemci 4 derleyici 4 baglayici 3 kosum 3 toplam gozlem: 14
Ölçümün Okunuşu
Tablonun dört bölümü dört ayrı bakış açısını gösteriyor. Ön işlemci bölümündeki dört satır
tip bilmez, yalnız metin bilir: makro değeri kaynağa metin olarak kondu, hangi koşullu
gövdenin derlemeye gireceği derleyici kaynağı görmeden belli oldu, iki parçadan yeni bir ad
üretildi ve başlık dosyasının bildirimleri kaynağa kopyalandı. Derleyici bölümündeki dört
satır ise yalnız tek bir birime bakarak karar veriyor: tip boyutunun birimi, sabit ifadenin
katlanması, numaralandırma sabitinin değeri ve bölme işlecinin hangi anlamı taşıyacağı hepsi
asama.c’nin kendi içinde çözüldü — hiçbiri secim.c’ye bakmadı.
Bağlayıcı bölümündeki üç satır bunun tam tersini gösteriyor: üçü de secim.c’de tanımlı bir
adı çağırıyor ve üçü de ancak iki birim bir araya gelince bir anlam kazanıyor. Tek başına
asama.c bu üç satırı derleyebilir, ama çalıştıramaz — eksik olan tanımlar yalnız bağlama
aşamasında tamamlanıyor. Koşum bölümündeki üç satır ise yalnız veriye bakıyor: toplam, dizgi
uzunluğu ve veriye bağlı dallanma hiçbiri kaynakta sabit bir sayı olarak durmuyor, üçü de
main çalışırken hesaplanıyor. Aynı kaynak farklı bir veri dizisiyle çalıştırılsaydı bu
üç satırın sonucu değişirdi; önceki üç bölümde bu mümkün değil, çünkü onların sonucu
kaynağın kendisinden — koşulmadan önce — bellidir.
DD1. gozle işlevi, tampon arabelleği ve dört sayaç (on_islemci, derleyici,
baglayici, kosum) çekirdeğin kendi tasarım kararıdır, C’nin bir düzeneği değildir. C’nin
kendisinde “bu satır hangi aşamaya ait” diye soran bir işlev yoktur; hangi satırın hangi
sınıfa girdiğine karar veren şey satırın kaynaktaki konumu ve biçimidir, gozle çağrısı
yalnız bu kararı bir satıra yazıp on dört gözlemi tek koşumda karşılaştırılabilir kılar.
DD2. Sayaçların dördü de sıradan int değişkenidir ve elle artırılır. C’nin çalışma
zamanı, bir satırın derlenirken hangi aşamaya ait olduğunu tutan bir kayıt taşımaz — bu
bilgi koşum başladığında zaten geçmişte kalmıştır. Dağılımı saymanın tek yolu, her gözlemin
yanına doğru sayacı elle eklemektir; sayım da bu yüzden çekirdeğin kaynağında, yani ön
işlemci ile derleyicinin göreceği metinde durur.
On dört gözlemin dördü ön işlemcide, dördü derleyicide, üçü bağlayıcıda, üçü koşumda karara bağlandı ve çıktı bu dağılımı doğrudan basıyor. Bu sayılar kursun geri kalanı için bağlayıcıdır: hiçbir ders bu dağılımı değiştirmez, yalnız kendi kesitini alır.
Aynı Karar, Dört Yer
Çekirdeğin on dört satırı rastgele seçilmedi; her biri aynı türden bir soruyu farklı bir
aşamada yanıtlıyor: bir değerin ne olduğu. KAT sabitinin değeri ön işlemcide belli
oluyor, çünkü #define bir metin değiştirme kuralıdır ve derleyici kaynağı görmeden önce
uygulanır. MAVI sabitinin değeri derleyicide belli oluyor, çünkü bir numaralandırma
(enum) üyesi derleme zamanı sabitidir ve tek bir birime bakılarak çözülür. birim_sayaci
değişkeninin değeri bağlayıcıda belli oluyor, çünkü değişken secim.c’de tanımlı ve
asama.c ona yalnız extern bir bildirimle erişiyor — ikisi ancak bağlanınca aynı adresi
paylaşıyor. toplam değişkeninin değeri ise koşumda belli oluyor, çünkü bir dizinin
elemanları üzerinde döngü yürütülmeden hesaplanamaz.
DD3. enum Renk üyelerine 1, 2, 4 değerlerinin açıkça verilmesi de bir kaynak
kararıdır, dilin zorunlu kıldığı bir şey değildir. Değer verilmeseydi C, üyeleri sıfırdan
başlayarak birer birer artırırdı (0, 1, 2); burada üyeler bilinçli olarak ikinin
kuvvetleri seçildi ki her biri ayrı bir bit temsil edebilsin. Bu, dördü değil, tek bir
kararı — bir sabitin sayısal değerini — ilgilendiriyor ve o karar da yine derleyici
aşamasında veriliyor.
Dördü de “bir değerin ne olduğu” sorusuna yanıt veriyor, ama dördü de farklı bir yerde
yanıtlıyor. Aynı soruyu sorduğun her seferde hangi aşamanın yanıt vereceğine karar veren
şey, o değeri kaynakta nasıl yazdığındır: bir #define yazarsan ön işlemci yanıtlar,
bir enum yazarsan derleyici yanıtlar, başka bir birimde tanımlı bir ad kullanırsan
bağlayıcı yanıtlar, bir diziyi dolaşırsan koşum yanıtlar. Programcı bu dördü seçebiliyor;
dilin kendisi hiçbirini dayatmıyor.
Bu örüntü kursun ilerleyen derslerinde başka biçimlerde tekrar karşımıza çıkacak. Aynı işi gören bir hesap Ön İşlemci dersinde bir makro olarak, Fonksiyonlar dersinde bağlayıcının çözdüğü bildirilmiş bir çağrı olarak yazılabilir; ikisi de “bu değer nedir” sorusuna yanıt verir, ama biri metin düzeyinde, öbürü birimler bir araya gelince. Kurs boyunca sorulacak soru değişmiyor: kaynakta yazılan bu karar hangi elde veriliyor?
Sınırlayıcı Ölçüm
Dört aşamanın varlığı bir karmaşıklık değil, dört ayrı denetim noktasıdır. Aynı türden bir
yanlışlık dört ayrı yerde ortaya çıkabilir ve dördü de aynı ağırlığı taşımaz. KAT
makrosuna yanlış bir sayı yazılırsa metin yanlış üretilir ve bu, kaynağın geri kalanına
sessizce yayılır — ön işlemci hiçbir denetim yapmaz, yalnız değiştirir. MAVI sabitine
yanlış bir değer verilirse derleyici bunu görebilir, çünkü tek bir birimin içinde kalan bir
tutarsızlıktır. secim.c’deki bir tanım silinirse hata derleyicide değil bağlayıcıda çıkar,
çünkü asama.c kendi başına derlenmeye devam eder — eksik olan yalnız birimler bir araya
gelince görünür. veri dizisine yanlış bir sayı eklenirse hiçbir aşama bunu yakalamaz;
sonuç yalnız o veriyle koşulunca ortaya çıkar.
Dördü de “yanlış değer” diye anılabilir, ama dördünün yakalandığı yer ayrıdır ve dördü de aynı gücü taşımaz. Ön işlemci ve derleyici hatası programın derlenmesini durdurabilir; bağlayıcı hatası programın bağlanmasını durdurabilir; koşum hatası ise yalnız o veriyle karşılaşıldığında görünür ve önceki üç aşamanın hiçbiri onu göremez. Aşama sayısının fazlalığı bu yüzden bir yük değil, bir avantajdır: aynı hata sınıfının nerede yakalanacağını seçmek, kaynağı nasıl yazdığına bağlıdır.
Bu sınır kursun geri kalanında bir uyarı olarak da okunmalı: bir aşamanın denetlemediği bir şey, bir sonraki aşamanın denetleyeceği anlamına gelmez. Ön işlemcinin geçtiği bir hata derleyiciye kalmaz; derleyicinin geçtiği bir hata bağlayıcıya kalmaz; bağlayıcının geçtiği bir hata da koşuma kalmaz — her aşama yalnız kendi girdisine bakar ve bir öncekinin ne yaptığını sorgulamaz. Dört aşamanın toplamı bu yüzden birbirini tamamlayan bir denetim zinciri değil, dördü de kendi sınırları içinde çalışan dört ayrı bakıştır.
Özet
- Bir C kaynağı programa dönüşürken dört aşamadan geçer: ön işlemci, derleyici, bağlayıcı, koşum; bu dört ad kurs boyunca değişmez.
- Üç dosyalık ölçüm çekirdeği on dört gözlemi tek koşumda sınıflandırdı: 4’ü ön işlemci, 4’ü derleyici, 3’ü bağlayıcı, 3’ü koşum.
- Aynı türden bir karar (bir değerin ne olduğu) dört aşamanın herhangi birinde verilebilir; hangisinin seçildiği kaynağın nasıl yazıldığına bağlıdır.
- Dört aşamanın varlığı karmaşıklık değil denetim noktası çokluğudur: aynı tür hata dört ayrı yerde, dört ayrı ağırlıkla yakalanabilir.
Sonraki Adım
Bu ders dört aşamayı adlandırdı ve tek bir koşumda yan yana gösterdi. Sıradaki ders aynı çekirdeğe geri dönüyor, ama bu kez dört sınıfı tek tek ele alıyor: her aşamanın kararını verirken hangi girdiye baktığı ve aşamaların neden geri dönüşü olmayan bir sırayla çalıştığı.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.