Derleme / bağlama süreci nasıl çalışır?


416

Derleme ve bağlama süreci nasıl çalışır?

(Not: Bu, Stack Overflow'ın C ++ SSS . Bu formda bir SSS sağlama fikrini eleştirmek istiyorsanız, tüm bunları başlatan metadaki yayınlama bunu yapmak için yer olacaktır. bu soru SSS fikrinin ilk başta başladığı C ++ sohbet odasında izlenir , bu nedenle cevabınızın bu fikri ortaya çıkaranlar tarafından okunması muhtemeldir.)

Yanıtlar:


554

Bir C ++ programının derlenmesi üç adımdan oluşur:

  1. Önişleme: önişlemci bir C ++ kaynak kodu dosyası alır ve #includes, #defines ve diğer önişlemci direktifleriyle ilgilenir. Bu adımın çıktısı, işlemci öncesi yönergeleri olmayan "saf" bir C ++ dosyasıdır.

  2. Derleme: derleyici ön işlemcinin çıktısını alır ve ondan bir nesne dosyası üretir.

  3. Bağlama: bağlayıcı derleyici tarafından üretilen nesne dosyalarını alır ve bir kütüphane ya da yürütülebilir bir dosya üretir.

Ön İşleme

Önişlemci ve gibi önişlemci yönergelerini işler . C ++ sözdiziminin agnostikidir, bu yüzden dikkatli kullanılmalıdır.#include#define

Bu değiştirerek her seferinde bir C ++ kaynak dosyası üzerinde çalışır #include, (genellikle sadece beyanlar olduğu) ilgili dosyaların içeriği ile direktiflerini makrolar (değiştirilmesini yapıyor #define) ve farklı metnin bölümlerini bağlı olarak seçilmesi #if, #ifdefve #ifndefdirektifleri.

Önişlemci, önişlem belirteçleri akışı üzerinde çalışır. Makro değiştirme, belirteçleri diğer belirteçlerle değiştirmek olarak tanımlanır (operatör ##mantıklı olduğunda iki belirtecin birleştirilmesini sağlar).

Tüm bunlardan sonra, önişlemci yukarıda açıklanan dönüşümlerden kaynaklanan bir token akışı olan tek bir çıktı üretir. Ayrıca derleyiciye her satırın nereden geldiğini bildiren bazı özel işaretler ekler, böylece bunları makul hata mesajları üretmek için kullanabilir.

Bu aşamada #ifve #errordirektiflerinin akıllıca kullanılmasıyla bazı hatalar üretilebilir .

Derleme

Derleme adımı önişlemcinin her çıktısında gerçekleştirilir. Derleyici saf C ++ kaynak kodunu ayrıştırır (şimdi herhangi bir önişlemci yönergesi olmadan) ve derleme koduna dönüştürür. Ardından, bu kodu makine koduna bir araya getiren ve bir biçimde gerçek ikili dosya (ELF, COFF, a.out, ...) üreten alt arka ucu (araç zincirinde birleştirici) çağırır. Bu nesne dosyası, girişte tanımlanan sembollerin derlenmiş kodunu (ikili biçimde) içerir. Nesne dosyalarındaki sembollere ad verilir.

Nesne dosyaları tanımlanmamış sembollere başvurabilir. Bir bildirim kullandığınızda ve bunun için bir tanım sağlamadığınızda durum budur. Derleyici buna aldırmaz ve kaynak kodu iyi biçimlendirilmiş olduğu sürece nesne dosyasını mutlu bir şekilde üretir.

Derleyiciler genellikle bu noktada derlemeyi durdurmanıza izin verir. Bu, her kaynak kod dosyasını ayrı olarak derleyebileceğiniz için çok kullanışlıdır. Bunun sağladığı avantaj, yalnızca tek bir dosyayı değiştirirseniz her şeyi yeniden derlemenize gerek olmamasıdır .

Oluşturulan nesne dosyaları, daha sonra yeniden kullanmak için statik kütüphaneler adı verilen özel arşivlere yerleştirilebilir.

Bu aşamada, sözdizimi hataları veya başarısız aşırı yük çözüm hataları gibi "normal" derleyici hataları bildirilir.

bağlayıcı

Bağlayıcı, derleyicinin ürettiği nesne dosyalarından son derleme çıktısını üreten şeydir. Bu çıktı, paylaşılan (veya dinamik) bir kitaplık olabilir (ve ad benzer olsa da, daha önce belirtilen statik kitaplıklarla pek ortak noktası yoktur) veya yürütülebilir bir dosya olabilir.

Tanımlanmamış sembollere yapılan referansları doğru adreslerle değiştirerek tüm nesne dosyalarını birbirine bağlar. Bu sembollerin her biri diğer nesne dosyalarında veya kütüphanelerde tanımlanabilir. Standart kitaplık dışındaki kitaplıklarda tanımlanmışlarsa, bağlayıcıya bu kitaplardan bahsetmeniz gerekir.

Bu aşamada en yaygın hatalar eksik tanımlar veya yinelenen tanımlardır. Birincisi, tanımların mevcut olmadığı (yani yazılmadıkları) veya ikamet ettikleri nesne dosyalarının veya kütüphanelerinin bağlayıcıya verilmediği anlamına gelir. İkincisi açıktır: Aynı sembol iki farklı nesne dosyasında veya kütüphanesinde tanımlanmıştır.


39
Derleme aşaması ayrıca nesne dosyasına dönüştürmeden önce derleyiciyi çağırır.
manav mn

3
Optimizasyonlar nereye uygulanır? İlk bakışta derleme adımında yapılacak gibi görünüyor, ancak öte yandan uygun optimizasyonun ancak bağlantı kurulduktan sonra yapılabileceğini hayal edebiliyorum.
Bart van Heukelom

6
@BartvanHeukelom geleneksel olarak derleme sırasında yapıldı, ancak modern derleyiciler çeviri birimleri arasında optimizasyon yapabilme avantajına sahip olan "bağlantı zamanı optimizasyonu" nu desteklemektedir.
R. Martinho Fernandes

3
C'nin aynı adımları var mı?
Kevin Zhu

6
Bağlayıcı, kütüphanelerdeki sınıflara / yönteme atıfta bulunan sembolleri adreslere dönüştürürse, bu, kütüphane ikili dosyalarının işletim sisteminin sabit tuttuğu bellek adreslerinde depolandığı anlamına mı gelir? Bağlayıcının, örneğin, tüm hedef sistemler için stdio ikilisinin tam adresini nasıl bileceği konusunda kafam karıştı. Dosya yolu her zaman aynı olurdu, ancak tam adres değişebilir, değil mi?
Dan Carter

42

Bu konu CProgramming.com adresinde tartışılmaktadır:
https://www.cprogramming.com/compilingandlinking.html

İşte yazarın yazdığı:

Derleme, yürütülebilir bir dosya oluşturmakla aynı şey değildir! Bunun yerine, yürütülebilir bir dosya oluşturmak iki bileşene ayrılmış çok aşamalı bir işlemdir: derleme ve bağlantı. Gerçekte, bir program "iyi derliyor" olsa bile, bağlantı aşaması sırasındaki hatalar nedeniyle aslında çalışmayabilir. Kaynak kod dosyalarından bir yürütülebilir dosyaya gitme işleminin tamamı derleme olarak adlandırılabilir.

Derleme

Derleme, kaynak kod dosyalarının (.c, .cc veya .cpp) işlenmesini ve bir 'nesne' dosyasının oluşturulmasını ifade eder. Bu adım, kullanıcının gerçekten çalıştırabileceği hiçbir şey oluşturmaz. Bunun yerine, derleyici yalnızca derlenen kaynak kod dosyasına karşılık gelen makine dili yönergelerini üretir. Örneğin, üç ayrı dosyayı derlerseniz (ancak bağlamıyorsanız), her biri .o veya .obj adında (uzantı derleyicinize bağlı olacaktır) çıktı olarak oluşturulmuş üç nesne dosyanız olacaktır. Bu dosyaların her biri kaynak kod dosyanızın bir makine dili dosyasına çevrilmesini içerir - ancak bunları henüz çalıştıramazsınız! Bunları işletim sisteminizin kullanabileceği yürütülebilir dosyalara dönüştürmeniz gerekir. Bağlayıcı devreye giriyor.

bağlayıcı

Bağlama, birden çok nesne dosyasından tek bir yürütülebilir dosyanın oluşturulmasını ifade eder. Bu adımda, bağlayıcının tanımlanmamış fonksiyonlardan (yaygın olarak ana kendisi) şikayet etmesi yaygındır. Derleme sırasında, derleyici belirli bir işlevin tanımını bulamazsa, işlevin başka bir dosyada tanımlandığını varsayar. Durum böyle değilse, derleyicinin bilmesi mümkün değildir - bir seferde birden fazla dosyanın içeriğine bakmaz. Diğer yandan bağlayıcı, birden fazla dosyaya bakabilir ve bahsedilmeyen işlevler için referanslar bulmaya çalışabilir.

Neden ayrı derleme ve bağlama adımları olduğunu sorabilirsiniz. İlk olarak, işleri bu şekilde uygulamak muhtemelen daha kolaydır. Derleyici işini yapar ve bağlayıcı işini yapar - işlevleri ayrı tutarak programın karmaşıklığı azalır. Bir başka (daha belirgin) avantaj, bunun, her dosya değiştirildiğinde derleme adımını yeniden yapmak zorunda kalmadan büyük programların oluşturulmasına izin vermesidir. Bunun yerine, "koşullu derleme" adı altında, yalnızca değişen kaynak dosyaları derlemek gerekir; geri kalanı için, nesne dosyaları bağlayıcı için yeterli girdi vardır. Son olarak, bu, önceden derlenmiş kod kitaplıklarının uygulanmasını kolaylaştırır: sadece nesne dosyaları oluşturun ve bunları diğer herhangi bir nesne dosyası gibi bağlayın.

Koşul derlemenin tüm avantajlarından yararlanmak için, size yardımcı olacak bir program almak, en son derlediğinizden beri hangi dosyaları değiştirdiğinizi hatırlamaktan daha kolay olabilir. (Elbette, karşılık gelen nesne dosyasının zaman damgasından daha büyük bir zaman damgası olan her dosyayı yeniden derleyebilirsiniz.) Tümleşik bir geliştirme ortamıyla (IDE) çalışıyorsanız, bununla zaten sizin için ilgilenebilir. Komut satırı araçları kullanıyorsanız, çoğu * nix dağıtımıyla birlikte gelen make adında şık bir yardımcı program vardır. Koşullu derleme ile birlikte, programınızın farklı derlemelerine izin vermek gibi programlama için başka güzel özelliklere de sahiptir - örneğin, hata ayıklama için ayrıntılı çıktı üreten bir sürümünüz varsa.

Derleme aşaması ile bağlantı aşaması arasındaki farkı bilmek, hataların avlanmasını kolaylaştırabilir. Derleyici hataları genellikle doğada sözdizimseldir - eksik bir noktalı virgül, fazladan bir parantez. Bağlantı hataları genellikle eksik veya çoklu tanımlarla ilgilidir. Bir işlev veya değişkenin bağlayıcıdan birden çok kez tanımlandığı şeklinde bir hata alırsanız, bu hata kaynak kodu dosyalarınızdan ikisinin aynı işleve veya değişkene sahip olduğunun iyi bir göstergesidir.


1
Anlamadığım şey, önişlemcinin #includes gibi bir süper dosya oluşturması gibi şeyleri yönetmesi durumunda, somurtkanın bundan sonra bağlanacak bir şey olmadığıdır.
binarysmacker

@binarysmacer Aşağıda yazdıklarımın bir anlamı olup olmadığını görün. Sorunu içten dışa anlatmaya çalıştım.
Eliptik görünüm

3
@binarysmacker Bu konuda yorum yapmak için çok geç, ancak diğerleri bunu faydalı bulabilir. youtu.be/D0TazQIkc8Q Temel olarak başlık dosyalarını dahil edersiniz ve bu başlık dosyaları genellikle sadece değişkenlerin / fonksiyonların açıklamalarını içerir ve tanımları değil, tanımları ayrı bir kaynak dosyasında olabilir. Değişken / işlevi kullanan kaynak dosyayı, bunları tanımlayan kaynak dosyaya bağlarsınız.
Karan Joisher

24

Standart cephede:

  • Bir çeviri birimi bir kaynak dosyaları dahil başlıkları ve kaynak dosyaları daha az koşullu içerme önişlemci direktifi ile atlanan herhangi bir kaynak hatlarının kombinasyonudur.

  • standart çeviride 9 aşama tanımlar. İlk dört ön işlemeye karşılık gelir, sonraki üç derleme, bir sonraki şablonların örneklenmesi ( örnekleme birimleri üretme ) ve sonuncusu bağlantıdır.

Pratikte sekizinci aşama (şablonların örneklenmesi) genellikle derleme işlemi sırasında yapılır, ancak bazı derleyiciler bunu bağlantı aşamasına erteler ve bazıları ikiye yayar.


14
9 aşamayı da listeleyebilir misiniz? Sanırım bu cevaba güzel bir katkı olurdu. :)
jalf


@jalf, sadece şablonun örneğini, sbi'nin işaret ettiği cevabın son aşamasından hemen önce ekleyin. IIRC, geniş karakterlerin işlenmesinde kesin ifadelerde ince farklılıklar vardır, ancak bunların diyagram etiketlerinde ortaya çıktığını düşünmüyorum.
AProgrammer

2
@sbi evet, ama bu SSS sorusu olmalı, değil mi? Peki bu bilgiler burada mevcut olmamalı mı? ;)
jalf

3
@AProgrammmer: basitçe isimleri ile listelemek yardımcı olacaktır. Daha sonra insanlar daha fazla ayrıntı istediklerinde ne arayacaklarını bilirler. Her neyse, her durumda cevabınızı + 1'ledim :)
jalf

14

Sıska, bir CPU'nun bellek adreslerinden veri yüklediği, verileri bellek adreslerine depoladığı ve talimatları işlem sırasına göre bazı koşullu sıçramalarla bellek adreslerinden sırayla yürütmesidir. Bu üç talimat kategorisinin her biri, makine talimatında kullanılacak bir bellek hücresine bir adres hesaplamayı içerir. Makine talimatları, ilgili talimatlara bağlı olarak değişken uzunlukta olduğundan ve makine kodumuzu oluştururken değişken uzunluklarını birbirine bağladığımız için, adresleri hesaplamak ve oluşturmak için iki aşamalı bir süreç vardır.

Öncelikle, her bir hücrede tam olarak ne olduğunu bilmeden önce bellek tahsisini elimizden gelenin en iyisini yapıyoruz. Baytları, kelimeleri veya talimatları, değişmez değerleri ve herhangi bir veriyi oluşturan her şeyi anlıyoruz. Biz sadece bellek ayırmaya ve programı devam ettirecek değerleri oluşturmaya başlıyoruz ve geri dönüp bir adresi düzeltmemiz gereken herhangi bir yeri not ediyoruz. Bu yerde, sadece bellek boyutunu hesaplamaya devam edebilmemiz için konumu doldurmak için bir kukla koyduk. Örneğin, ilk makine kodumuz bir hücre alabilir. Bir sonraki makine kodu, bir makine kodu hücresi ve iki adres hücresi içeren 3 hücre alabilir. Şimdi adres işaretçimiz 4'tür. Op hücresi olan makine hücresine ne girdiğini biliyoruz, ancak bu verilerin nerede bulunacağını bilinceye kadar adres hücrelerinde ne olduğunu hesaplamak için beklemek zorundayız, yani

Sadece bir kaynak dosya olsaydı, bir derleyici teorik olarak bir bağlayıcı olmadan tamamen çalıştırılabilir makine kodu üretebilir. İki geçişli bir işlemde, herhangi bir makine yükleme veya depolama talimatı tarafından başvurulan tüm veri hücrelerine tüm gerçek adresleri hesaplayabilir. Ve herhangi bir mutlak atlama talimatı tarafından başvurulan mutlak adresleri hesaplayabilir. Forth'daki gibi, daha basit derleyiciler hiçbir bağlayıcı olmadan çalışır.

Bağlayıcı, kod bloklarının ayrı olarak derlenmesine izin veren bir şeydir. Bu, genel kod oluşturma sürecini hızlandırabilir ve blokların daha sonra nasıl kullanıldığı konusunda biraz esnekliğe izin verir, diğer bir deyişle bellekte yeniden konumlandırılabilir, örneğin bloğu 1000 adres hücresi ile puanlamak için her adrese 1000 ekleme.

Derleyicinin çıktıları henüz tam olarak inşa edilmemiş kaba makine kodudur, ancak her şeyin boyutunu biliyoruz, diğer bir deyişle, tüm mutlak adreslerin nerede bulunacağını hesaplamaya başlayabiliriz. derleyici ayrıca ad / adres çiftleri olan sembollerin bir listesini çıkarır. Semboller, modüldeki makine kodundaki bir bellek ofsetini bir adla ilişkilendirir. Ofset, modül içindeki sembolün bellek konumuna olan mutlak mesafedir.

Burada bağlayıcıya ulaşıyoruz. Bağlayıcı ilk önce tüm bu makine kodu bloklarını uçtan uca tokatlar ve her birinin başladığı yeri not eder. Ardından, bir modül içindeki göreli ofseti ve modülün daha büyük düzende mutlak konumunu toplayarak sabitlenecek adresleri hesaplar.

Açıkçası bunu çok basitleştirdim, böylece kavramaya çalışabilirsiniz ve bence karışıklığın bir parçası olan nesne dosyalarının, sembol tablolarının vb. Jargonunu kullanmadım.


13

GCC, bir C / C ++ programını yürütülebilir olarak 4 adımda derler.

Örneğin, gcc -o hello hello.caşağıdaki gibi gerçekleştirilir:

1. Ön işleme

cpp.exeBaşlıkları ( #include) içeren ve makroları ( #define) genişleten GNU C Önişlemcisi ( ) ile önişleme .

cpp hello.c > hello.i

Sonuçta ortaya çıkan ara dosya "hello.i" genişletilmiş kaynak kodunu içerir.

2. Derleme

Derleyici, önceden işlenmiş kaynak kodunu belirli bir işlemci için montaj koduna derler.

gcc -S hello.i

-S seçeneği, nesne kodu yerine montaj kodu üretmeyi belirtir. Sonuçta elde edilen derleme dosyası "merhaba.s" dir.

3. Meclis

as.exeAssemblyer ( ), "hello.o" nesne dosyasında derleme kodunu makine koduna dönüştürür.

as -o hello.o hello.s

4. Bağlayıcı

Son olarak, linker ( ld.exe), "merhaba" yürütülebilir bir dosya üretmek için nesne kodunu kütüphane koduyla ilişkilendirir.

    ld -o merhaba hello.o ... kütüphaneler ...

9

URL'ye bakın: http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html
C ++ 'ın tamamlama süreci bu URL'de açıkça belirtilmiştir.


2
Bunu paylaştığınız için teşekkürler, anlaşılması çok basit ve anlaşılır.
Mark

İyi, kaynak, sürecin bazı temel açıklamalarını buraya koyabilir misiniz, cevap algoritma tarafından düşük kaliteli b / c kısa ve sadece url olarak işaretlenir.
JasonB

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.