🔨 Translation Phases - Çevirim Aşamaları
Bu yazıda bir C programı çevirilirken olanlara biraz daha detaylı bakacağız. Okumadıysanız önce 🏗️ Bir C Programı Derlenirken Olanlar yazısını okumanızı öneririm.
Burada çevirim kelimesini İngilizce translation kelimesinin karşılığı olarak kullandım. Bilgisayar dünyasında translator kelimesi kullanılmaktadır. Translator, bir programlama dilini başka bir programlama diline çeviren araç anlamındadır. Eğer bu süreçte hedef dil, düşük seviyeli dil ise o zaman bu işi yapan translator’lara compiler adını vermekteyiz. C’de de C kodları, (en) düşük seviyeli bir hedef olan saf makina diline dönüştürülmektedir. Bu yüzden bu işlem compilation olarak adlandırılır fakat aynı zamanda bir translation işlemi de yapılır.
Peki bu aşamada bir C dosyasının başına neler gelmektedir?
Not
Birazdan bahsedeceğimiz adımlar derleyici tarafından takip edilmelidir. Fakat derleyiciler isterlerse bu adımların bazılarını birleştirip tek bir adım olarak yapabilirler. Önemli olan aşağıdaki sıranın bozulmadığını garanti etmektir. Biz programcılar olarak derleyicinin nasıl gerçekleştirildiğinden bağımsız olarak aşağıdaki sıranın takip edildiğini varsayabiliriz.
Faz 1
İlk olarak kaynak kodda bulunan byte’lar, ki bu kaynak kod bir metin dosyası olmaktadır tipik olarak ve UTF-8 gibi multibyte karakter kodlaması içerebilir, source character set te bulunan karakterlere “implementation-defined” olarak eşlenmektedir. Yani daha anlaşılır anlatımla, kaynak kodun içerisindeki byte’lar karakter kodları olarak anlamlandırılır. Bu eşleşme, mapping, kullanılan karakter kodlama sisteminin bir sonucudur.
Bknz: 🔤 Character Set(s) - Karakter Set(leri)
Source character set bir multibyte character set’tir ve 96 karakterden oluşan, single-byte olarak oluşturulmuş basic source character set i içermelidir.
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z
0 1 2 3 4 5 6 7 8 9
! " # % & ' ( ) * + , - . / : ; < = > ? [ \ ] ^ _ { | } ~
<Space> <TAB> <VT> <FF> <LF ?>
Not
Bu karakterlerin toplam sayısı 96 yapmaktadır. Ama C standardlarına göre 0x0A
kodlu LF
karakteri basic source character set içerisinde değildir. Ama
iyice suyunu çıkartmayalım artık…
Unlike C++, the U+000A LINE FEED (LF) character is not included in basic character set. Instead, there shall be some way of indicating the end of each line of text in the source file and the document treats such an end-of-line indicator as if it were a single new-line character.
Basic character set is also known as basic source character set.
Burada bizim için önemli olan noktalardan biri de kaynak dosyadaki işletim sistemi
spesifik satır sonu belirteçlerinin, Windows’ta CRLF
gibi, Linux’ta LF
gibi
newline karakterlerine dönüştürülmüş olmasıdır. Bildiğim kadarıyla C standartları
newline karakterleri konusunda net bir açıklama yapmıyor, bunu biraz soyut bir
kavram olarak bırakıyor. Ama LF
olarak düşünmek de bence çok yanlış değil.
Özetle kodu yazdığımız platformdan, Windows/Linux/Mac, bağımsız olarak satır
sonları anlaşılıyor diyebiliriz.
Daha sonra trigraph sequence dediğimiz ??x
formundaki 3’lü karakter grupları,
tekli karakter gösterimlerine çevrilir. Bu olay C23 ile kaldırılmıştır. Zaten
kim artık buna ihtiyaç duyuyordur bilmiyorum. Bknz: 🔤 Character Set(s) - Karakter Set(leri)
Faz 2
Eğer bir satırın sonunda ters bölü karakteri \
ve hemen arkasında
newline character varsa, yani satır sonunda \
varsa ve hemen alt satıra
geçtiysek burada iki karakter birden silinir ve iki satır birleştirilmiş
olur. Bu işlem bir kere yapılır, iteratif olarak bitene kadar yapılmaz.
#define PUTS p\
u\
t\
s
//alttakiyle eş değer
#define PUTS puts
//Sentaks hatası, birden fazla tur dönülmüyor çünkü
x = 5;\\
y = 4;
C kurallarına göre boş olmayan bir kaynak kod dosyası, newline character ile
bitmelidir. Eğer bu işlemden sonra yani \
işledik diyelim, dosya sonunda
newline character yoksa bu tanımsız davranış, UB oluşturmaktadır.
Faz 3
Burada dosya artık işlenerek adeta “parçalanır”. Dosya yorumlar, whitespace
karakterlere yani SPACE
, TAB
, LF
, VT
, FF
, ve token yani atomlara
ayrılır. Atomlara ayrılırken başlık dosyaları, header file isimleri,
identifier’lar, sabitler vs belirlenir.
Her bir yorum tek bir boşluk, SPACE
, karakteri ile yer değiştirilir.
New line karakteri tutulur. New line karakterinden oluşmayan whitespace sekanslarının birleştirilip tek bir space karakterine düşürülüp düşürülmeyeceği implementation defined bir durumdur.
Not
Buradaki ayrılan tokenların tam doğru adı aslında preprocessing token dır ve C tokenlardan biraz farklıdır. Daha preprocessor yani ön işlemci çalışmadı farkındaysak. Ama iki token kategorisi arasındaki farka girmeyelim.
Burada önemli bir kural olan maximal munch kuralından söz etmeden olmaz. Derleyici, token oluştururken yani kaynak koddaki karakterleri anlamlandırırken karakterlerden en uzun parçalardan bir token oluşturmaya çalışır, devamında bu bir sentaks hatası yaratacak olsa bile. Örneğin:
int main(void)
{
int x = 1, y = 2, z;
z = (x++)+(++y);
}
gibi bir kod yazdık diyelim. Burada z
nin değeri 4
olacaktı. Eğer öncelik
parantezlerini kullanmasaydık yani z = x+++++y;
yazsaydık bu bir sentaks
hatası oluşturacaktı. Çünkü bu kuralara göre bu ifade z = x++ ++ +y;
olarak
atomlarına ayrılacaktı, bu da geçerli bir sentaks değildir. Derleyici burada
ya bu sentaks hatası olur, başka türlü yapayım demiyor işte… 🤷
int x = 10/*Bu yorum tek bir boşluk karakteri olacak*/+ 5; //OK, 10 + 5;
int x = y+++z; //OK, y++ + z;
int x = y++-++z; //OK y++ - ++z. -+ diye operatör yok çünkü.
int x = y+++ ++z; //OK y++ + ++Z;
int x = y+++/*tek boşluk olacak, üstteki ile aynı*/++z; //OK
Buna tek istisna #include
dışındaki yerlerde <>
karakterlerinin header name
preprocessing token oluşturmamasıdır. Yani derleyici her herde <>
görünce
“atlamaz”
#define MACRO_1 1
#define MACRO_2 2
#define MACRO_3 3
#define MACRO_EXPR (MACRO_1 <MACRO_2> MACRO_3) // OK: <MACRO_2> is not a header-name
Faz 4
Bu aşamada ilk olarak önişlemci yani preprocessor çalışır. Eğer #include
önişlemci komutu ile dahil ettiğimiz dosyalar varsa her bir dosya için recursive
olarak, #include
edilen dosya başka dosyaları da #include
ediyor olabilir,
Faz 1-4 arası çalıştırılır.
Bu aşamanın sonunda kaynak kodda hiçbir önişlemci komutu yani #
ile
başlayanlar kalmaz.
Faz 5
Bu aşamada source character set ten, execution character set e çevrim yapılır.
Bknz: 🔤 Character Set(s) - Karakter Set(leri)
Source charactet set, bizim kodu yazdığımız bilgisayarda kullanılan karakter seti iken execution character set, derlenmiş programın çalışacağı bilgisayarın karakter setidir. Günümüzde belki çok anlamlı olmasa da örneğin ASCII karakter kodlaması kullanılan bir bilgisayarda kodumuzu geliştiriyor ve derlenen kod EBCDIC kodlamasını kullanan bir bilgisayarda çalışıyor olabilirdi. Kaynak kodda gördüğümüz yazıların, örneğin string literalların, hedef bilgisayarda da kod çalışınca düzgün gözükmesi için bu çevrimin yapılması gerekmektedir.
Bu çevrim karakter sabitlerinin, 'a'
gibi, ve string literallerin, "alper"
gibi, içerisindeki karakterlerin ve escape seqeunceların, \n
gibi, tümünü
kapsamaktadır.
Execution character seti’nde bahsettiğimiz temel 96 karakterden oluşan, <LF>
?,
basic source character set bulunmalı ve 1-byte ile ifade edilmelidir. Diğer
karakterler UTF-8 gibi multi-byte olabilirler.
Eğer escape sequence ile belirttiğimiz bir karakterin execution character set’te bir karşılığı yoksa bu çevrimin nasıl yapacağı implementation-defined bırakılmıştır. Fakat bu dönüşüm sonucunda null character elde edilmeyeceği garanti edilmiştir (sanıyorum wide karakterlerde de böyle)
Bu çevrim, GCC ve Clang’in desteklediği -finput-charset
, -fexec-charset
,
-fwide-exec-charset
flagleri ile kontrol edilebilmektedir.
Faz 6
Ard arda gelen string literaller birleştirilir ve tek bir string literale dönüştürülür.
#include <stdio.h>
int main(void)
{
puts("Alper" "Yazar"); // Tek bir string literal "AlperYazar"
return 0;
}
Yukarıdaki kod bu sebebpten dolayı puts("AlperYazar")
ile eşdeğerdir. Bu,
garip bir özellik gibi gelebilir ama C’de idiomatic kod yazarken kullanışlı
olduğu trickli yerler vardır.
Faz 7
Derleme işlemi artık yapılır. Her bir kaynak kod, daha doğrusu translation unit, bağımsız olarak derlenir. String literallerin sonuna terminating null character konması ve string literallerin statik ömüre sahip isimsiz bir array olarak tutulması işlemi de bu aşamada yapılır. [1]
Faz 8
Derleme işlemi bitip, elimizde obje code’lar oluştuktan sonra linker tüm bağımsız derlenmiş çıktıları birleştirir ve bir adet dosya oluşur.
Özellikle Faz 7 ve 8 adımları 🏗️ Bir C Programı Derlenirken Olanlar yazısında daha detaylı anlatılmıştır.
💭 Yorumlar
Yorum altyapısı giscus tarafından (evet tarafından!) sağlanmaktadır. Yorum yazabilmek için GitHub hesabınız üzerinden giriş yapmanız gerekmektedir. Yorumlar, Github Discussions üzerinde saklanmaktadır.
1c100800-9bf7-4b6b-9cce-5eedb05d6c13