Bagaimana cara kerja proses kompilasi / penautan?

416

Bagaimana cara proses kompilasi dan penautan bekerja?

(Catatan: Ini dimaksudkan sebagai entri untuk FAQ C ++ Stack Overflow . Jika Anda ingin mengkritik gagasan memberikan FAQ dalam formulir ini, maka posting pada meta yang memulai semua ini akan menjadi tempat untuk melakukan itu. Jawaban untuk pertanyaan itu dipantau di chatroom C ++ , di mana ide FAQ dimulai sejak awal, jadi jawaban Anda sangat mungkin untuk dibaca oleh mereka yang mengemukakan ide itu.)

tidak diketahui
sumber

Jawaban:

554

Kompilasi program C ++ melibatkan tiga langkah:

  1. Preprocessing: preprocessor mengambil file kode sumber C ++ dan berkaitan dengan #includes, #defines dan arahan preprocessor lainnya. Output dari langkah ini adalah file C ++ "murni" tanpa arahan pra-prosesor.

  2. Kompilasi: kompiler mengambil output pra-prosesor dan menghasilkan file objek darinya.

  3. Menautkan: tautan mengambil file objek yang dihasilkan oleh kompiler dan menghasilkan pustaka atau file yang dapat dieksekusi.

Preprocessing

Preprocessor menangani arahan preprocessor , seperti #includedan #define. Ini agnostik dari sintaksis C ++, oleh karena itu harus digunakan dengan hati-hati.

Ia bekerja pada satu file sumber C ++ pada satu waktu dengan mengganti #includearahan dengan konten dari masing-masing file (yang biasanya hanya deklarasi), melakukan penggantian makro ( #define), dan memilih bagian teks yang berbeda tergantung dari #if, #ifdefdan #ifndefarahan.

Preprocessor bekerja pada aliran token preprocessing. Substitusi makro didefinisikan sebagai mengganti token dengan token lain (operator ##memungkinkan penggabungan dua token saat masuk akal).

Setelah semua ini, preprocessor menghasilkan output tunggal yang merupakan aliran token yang dihasilkan dari transformasi yang dijelaskan di atas. Itu juga menambahkan beberapa spidol khusus yang memberitahu kompiler dari mana setiap baris berasal sehingga dapat menggunakannya untuk menghasilkan pesan kesalahan yang masuk akal.

Beberapa kesalahan dapat dihasilkan pada tahap ini dengan penggunaan #ifdan #errorarahan yang cerdas .

Kompilasi

Langkah kompilasi dilakukan pada setiap output preprosesor. Compiler mem-parsing kode sumber C ++ murni (sekarang tanpa arahan preprocessor) dan mengubahnya menjadi kode assembly. Kemudian gunakan back-end yang mendasari (assembler dalam toolchain) yang merakit kode itu ke dalam kode mesin yang menghasilkan file biner aktual dalam beberapa format (ELF, COFF, a.out, ...). File objek ini berisi kode yang dikompilasi (dalam bentuk biner) dari simbol-simbol yang didefinisikan dalam input. Simbol dalam file objek disebut dengan nama.

File objek dapat merujuk ke simbol yang tidak didefinisikan. Ini adalah kasus ketika Anda menggunakan deklarasi, dan tidak memberikan definisi untuk itu. Compiler tidak keberatan dengan hal ini, dan dengan senang hati akan menghasilkan file objek selama kode sumbernya terbentuk dengan baik.

Compiler biasanya membiarkan Anda menghentikan kompilasi pada saat ini. Ini sangat berguna karena dengan itu Anda dapat mengkompilasi setiap file kode sumber secara terpisah. Keuntungan yang diberikan ini adalah Anda tidak perlu mengkompilasi ulang semuanya jika Anda hanya mengubah satu file.

File objek yang dihasilkan dapat dimasukkan ke dalam arsip khusus yang disebut perpustakaan statis, untuk digunakan kembali nanti.

Pada tahap ini dilaporkan kesalahan "reguler", seperti kesalahan sintaksis atau kesalahan resolusi kelebihan beban yang dilaporkan.

Menautkan

Tautan adalah yang menghasilkan keluaran kompilasi akhir dari file objek yang dihasilkan kompiler. Output ini bisa berupa pustaka bersama (atau dinamis) (dan meskipun namanya mirip, pustaka tersebut tidak memiliki banyak kesamaan dengan pustaka statis yang disebutkan sebelumnya) atau yang dapat dieksekusi.

Ini menautkan semua file objek dengan mengganti referensi ke simbol yang tidak terdefinisi dengan alamat yang benar. Masing-masing simbol ini dapat didefinisikan dalam file objek lain atau di perpustakaan. Jika mereka didefinisikan di perpustakaan selain dari perpustakaan standar, Anda perlu memberi tahu linker tentang mereka.

Pada tahap ini kesalahan yang paling umum adalah definisi yang hilang atau definisi duplikat. Yang pertama berarti bahwa definisi tidak ada (yaitu mereka tidak ditulis), atau bahwa file objek atau pustaka tempat mereka berada tidak diberikan kepada linker. Yang terakhir jelas: simbol yang sama didefinisikan dalam dua file objek atau pustaka yang berbeda.

R. Martinho Fernandes
sumber
39
Tahap kompilasi juga memanggil assembler sebelum mengkonversi ke file objek.
manav mn
3
Di mana optimasi diterapkan? Pada pandangan pertama sepertinya akan dilakukan pada langkah kompilasi, tetapi di sisi lain saya dapat membayangkan bahwa optimasi yang tepat hanya dapat dilakukan setelah menghubungkan.
Bart van Heukelom
6
@BartvanHeukelom secara tradisional itu dilakukan selama kompilasi, tetapi kompiler modern mendukung apa yang disebut "optimisasi tautan-waktu" yang memiliki keuntungan untuk dapat mengoptimalkan lintas unit terjemahan.
R. Martinho Fernandes
3
Apakah C memiliki langkah yang sama?
Kevin Zhu
6
Jika tautan mengubah simbol yang merujuk ke kelas / metode di pustaka menjadi alamat, apakah itu berarti bahwa pustaka biner disimpan dalam alamat memori yang OS-nya tetap konstan? Saya hanya bingung bagaimana linker akan mengetahui alamat pasti, katakanlah, binari stdio untuk semua sistem target. Path file akan selalu sama, tetapi alamat yang tepat dapat berubah, bukan?
Dan Carter
42

Topik ini dibahas di CProgramming.com:
https://www.cprogramming.com/compilingandlinking.html

Inilah yang penulis tulis di sana:

Kompilasi tidak sama dengan membuat file yang dapat dieksekusi! Sebaliknya, membuat executable adalah proses bertingkat yang dibagi menjadi dua komponen: kompilasi dan penautan. Pada kenyataannya, bahkan jika suatu program "mengkompilasi dengan baik" itu mungkin tidak benar-benar berfungsi karena kesalahan selama fase penautan. Total proses dari file kode sumber ke file yang dapat dieksekusi mungkin lebih baik disebut sebagai build.

Kompilasi

Kompilasi mengacu pada pemrosesan file kode sumber (.c, .cc, atau .cpp) dan pembuatan file 'objek'. Langkah ini tidak menciptakan apa pun yang dapat dijalankan oleh pengguna. Sebagai gantinya, kompiler hanya menghasilkan instruksi bahasa mesin yang sesuai dengan file kode sumber yang dikompilasi. Sebagai contoh, jika Anda mengkompilasi (tetapi tidak menautkan) tiga file terpisah, Anda akan memiliki tiga file objek yang dibuat sebagai output, masing-masing dengan nama .o atau .obj (ekstensi akan tergantung pada kompiler Anda). Masing-masing file ini berisi terjemahan file kode sumber Anda ke file bahasa mesin - tetapi Anda belum dapat menjalankannya! Anda harus mengubahnya menjadi executable yang dapat digunakan oleh sistem operasi Anda. Di situlah linker masuk.

Menautkan

Menautkan mengacu pada pembuatan file yang dapat dieksekusi tunggal dari banyak file objek. Pada langkah ini, adalah umum bahwa linker akan mengeluh tentang fungsi yang tidak terdefinisi (umumnya, utama itu sendiri). Selama kompilasi, jika kompiler tidak dapat menemukan definisi untuk fungsi tertentu, itu hanya akan menganggap bahwa fungsi itu didefinisikan dalam file lain. Jika ini bukan masalahnya, kompiler tidak akan tahu - kompiler itu tidak melihat isi lebih dari satu file sekaligus. Linker, di sisi lain, dapat melihat beberapa file dan mencoba mencari referensi untuk fungsi yang tidak disebutkan.

Anda mungkin bertanya mengapa ada langkah kompilasi dan tautan yang terpisah. Pertama, mungkin lebih mudah untuk mengimplementasikan hal-hal seperti itu. Compiler melakukan tugasnya, dan penghubung melakukan tugasnya - dengan memisahkan fungsi-fungsi, kompleksitas program berkurang. Keuntungan lain (yang lebih jelas) adalah ini memungkinkan pembuatan program besar tanpa harus mengulang langkah kompilasi setiap kali file diubah. Sebagai gantinya, dengan menggunakan apa yang disebut "kompilasi bersyarat", perlu untuk mengkompilasi hanya file sumber yang telah berubah; selebihnya, file objek adalah input yang cukup untuk linker. Akhirnya, ini membuatnya mudah untuk mengimplementasikan pustaka kode pra-kompilasi: cukup buat file objek dan tautkan seperti file objek lainnya.

Untuk mendapatkan manfaat kompilasi kondisi sepenuhnya, mungkin lebih mudah mendapatkan program untuk membantu Anda daripada mencoba dan mengingat file mana yang telah Anda ubah sejak terakhir kali dikompilasi. (Tentu saja, Anda dapat mengkompilasi ulang setiap file yang memiliki stempel waktu lebih besar dari stempel waktu dari file objek yang sesuai.) Jika Anda bekerja dengan lingkungan pengembangan terintegrasi (IDE), mungkin ini sudah menangani ini untuk Anda. Jika Anda menggunakan alat baris perintah, ada utilitas bagus bernama make yang datang dengan sebagian besar * distribusi nix. Bersamaan dengan kompilasi bersyarat, ia memiliki beberapa fitur bagus untuk pemrograman, seperti memungkinkan kompilasi yang berbeda dari program Anda - misalnya, jika Anda memiliki versi yang menghasilkan output verbose untuk debugging.

Mengetahui perbedaan antara fase kompilasi dan fase tautan dapat membuatnya lebih mudah untuk mencari bug. Kesalahan kompiler biasanya sintaksis di alam - titik koma yang hilang, tanda kurung tambahan. Menghubungkan kesalahan biasanya berkaitan dengan definisi yang hilang atau banyak. Jika Anda mendapatkan kesalahan bahwa suatu fungsi atau variabel didefinisikan beberapa kali dari linker, itu indikasi yang baik bahwa kesalahan adalah bahwa dua file kode sumber Anda memiliki fungsi atau variabel yang sama.

neuronet
sumber
1
Apa yang saya tidak mengerti adalah bahwa jika preprocessor mengelola hal-hal seperti #includes untuk membuat satu file super maka bermuka masam tidak ada yang menghubungkan setelah itu?
binarysmacker
@ binarysmacer Lihat apa yang saya tulis di bawah ini masuk akal bagi Anda. Saya mencoba menggambarkan masalahnya dari dalam ke luar.
Tampilan elips
3
@ binarysmacker Sudah terlambat untuk mengomentari ini, tetapi orang lain mungkin menemukan ini berguna. youtu.be/D0TazQIkc8Q Pada dasarnya Anda menyertakan file header dan file header ini umumnya hanya berisi deklarasi variabel / fungsi dan tidak ada definisi, definisi mungkin hadir dalam file sumber terpisah. Jadi preprosesor hanya menyertakan deklarasi dan bukan definisi di mana ini linker help. Anda menautkan file sumber yang menggunakan variabel / fungsi dengan file sumber yang mendefinisikannya.
Karan Joisher
24

Di depan standar:

  • sebuah unit terjemahan adalah kombinasi dari file sumber, header termasuk dan file sumber dikurangi garis sumber dilewati oleh bersyarat inklusi preprocessor direktif.

  • standar mendefinisikan 9 fase dalam terjemahan. Empat yang pertama berhubungan dengan preprocessing, tiga berikutnya adalah kompilasi, yang berikutnya adalah instantiation dari templat (menghasilkan unit instantiation ) dan yang terakhir adalah menghubungkan.

Dalam praktiknya fase kedelapan (instantiation of templates) sering dilakukan selama proses kompilasi tetapi beberapa kompiler menunda ke fase penghubung dan beberapa menyebar di keduanya.

Pemrogram
sumber
14
Bisakah Anda mendaftar semua 9 fase? Itu akan menjadi tambahan yang bagus untuk jawabannya, saya pikir. :)
jalf
@jalf, tambahkan saja instantiasi template tepat sebelum fase terakhir dalam jawaban yang ditunjukkan oleh @sbi. IIRC ada perbedaan halus dalam kata-kata yang tepat dalam penanganan karakter lebar, tapi saya tidak berpikir mereka muncul di label diagram.
Pemrogram
2
@ SBI ya, tapi ini seharusnya menjadi pertanyaan FAQ, bukan? Jadi bukankah informasi ini tersedia di sini ? ;)
jalf
3
@AProgrammmer: cukup daftar mereka dengan nama akan sangat membantu. Lalu orang tahu apa yang harus dicari jika mereka ingin lebih detail. Bagaimanapun, memberi +1 jawaban Anda dalam hal apa pun :)
jalf
14

Kurusnya adalah CPU memuat data dari alamat memori, menyimpan data ke alamat memori, dan menjalankan instruksi secara berurutan dari alamat memori, dengan beberapa lompatan kondisional dalam urutan instruksi yang diproses. Masing-masing dari tiga kategori instruksi ini melibatkan penghitungan alamat ke sel memori yang akan digunakan dalam instruksi mesin. Karena instruksi mesin adalah panjang variabel tergantung pada instruksi tertentu yang terlibat, dan karena kami merangkai panjang variabel mereka bersama-sama saat kami membangun kode mesin kami, ada dua langkah proses yang terlibat dalam menghitung dan membangun alamat apa pun.

Pertama kita meletakkan alokasi memori sebaik mungkin sebelum kita bisa tahu apa yang sebenarnya terjadi di setiap sel. Kami mencari tahu byte, atau kata-kata, atau apa pun yang membentuk instruksi dan literal serta data apa pun. Kami baru saja mulai mengalokasikan memori dan membangun nilai-nilai yang akan membuat program saat kami pergi, dan mencatat di mana saja kita perlu kembali dan memperbaiki alamat. Di tempat itu kami meletakkan boneka hanya pad lokasi sehingga kami dapat terus menghitung ukuran memori. Misalnya kode mesin pertama kami mungkin mengambil satu sel. Kode mesin berikutnya mungkin mengambil 3 sel, yang melibatkan satu sel kode mesin dan dua sel alamat. Sekarang pointer alamat kami adalah 4. Kita tahu apa yang terjadi di sel mesin, yang merupakan kode op, tetapi kita harus menunggu untuk menghitung apa yang terjadi di sel-sel alamat sampai kita tahu di mana data itu akan ditemukan, yaitu

Jika hanya ada satu file sumber kompiler secara teoritis dapat menghasilkan kode mesin yang sepenuhnya dapat dieksekusi tanpa linker. Dalam dua proses lulus, ini bisa menghitung semua alamat aktual untuk semua sel data yang dirujuk oleh setiap mesin memuat atau menyimpan instruksi. Dan itu bisa menghitung semua alamat absolut yang dirujuk oleh instruksi lompatan absolut. Ini adalah bagaimana kompiler yang lebih sederhana, seperti yang ada di Forth bekerja, tanpa tautan.

Linker adalah sesuatu yang memungkinkan blok kode dikompilasi secara terpisah. Ini dapat mempercepat proses keseluruhan kode bangunan, dan memungkinkan beberapa fleksibilitas dengan bagaimana blok kemudian digunakan, dengan kata lain mereka dapat dipindahkan di memori, misalnya menambahkan 1000 ke setiap alamat untuk menggeser blok dengan 1000 sel alamat.

Jadi apa yang dihasilkan oleh kompiler adalah kode mesin kasar yang belum sepenuhnya dibangun, tetapi ditata sedemikian sehingga kita mengetahui ukuran segalanya, dengan kata lain sehingga kita dapat mulai menghitung di mana semua alamat absolut akan berada. kompiler juga menampilkan daftar simbol yang merupakan pasangan nama / alamat. Simbol menghubungkan offset memori dalam kode mesin dalam modul dengan nama. Offset menjadi jarak absolut ke lokasi memori simbol dalam modul.

Di situlah kita sampai ke penghubung. Linker pertama-tama menampar semua blok kode mesin ini bersama-sama dari ujung ke ujung dan mencatat di mana masing-masing dimulai. Kemudian ia menghitung alamat yang akan diperbaiki dengan menambahkan offset relatif dalam sebuah modul dan posisi absolut modul dalam tata letak yang lebih besar.

Jelas saya sudah terlalu menyederhanakan ini sehingga Anda dapat mencoba untuk menangkapnya, dan saya sengaja tidak menggunakan jargon file objek, tabel simbol, dll yang bagi saya merupakan bagian dari kebingungan.

nama pengguna saya dibajak di sini
sumber
13

GCC mengkompilasi program C / C ++ menjadi executable dalam 4 langkah.

Sebagai contoh, gcc -o hello hello.cdilakukan sebagai berikut:

1. Pra-pemrosesan

Preprocessing melalui GNU C Preprocessor ( cpp.exe), yang mencakup header ( #include) dan memperluas makro ( #define).

cpp hello.c > hello.i

File perantara yang dihasilkan "hello.i" berisi kode sumber yang diperluas.

2. Kompilasi

Kompiler mengkompilasi kode sumber yang sudah diproses menjadi kode perakitan untuk prosesor tertentu.

gcc -S hello.i

Opsi -S menentukan untuk menghasilkan kode rakitan, bukan kode objek. File rakitan yang dihasilkan adalah "hello.s".

3. Majelis

Assembler ( as.exe) mengubah kode rakitan menjadi kode mesin di file objek "hello.o".

as -o hello.o hello.s

4. Tautan

Akhirnya, linker ( ld.exe) menautkan kode objek dengan kode perpustakaan untuk menghasilkan file yang dapat dieksekusi "halo".

    lo -o halo hello.o ... perpustakaan ...
kaps
sumber
9

Lihatlah URL: http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html
Proses kompilasi lengkap C ++ diperkenalkan dengan jelas di URL ini.

Charles Wang
sumber
2
Terima kasih telah membagikannya, sangat sederhana dan mudah dimengerti.
Markus
Bagus, sumber daya, dapatkah Anda memberikan beberapa penjelasan dasar tentang proses di sini, jawabannya ditandai oleh algoritma karena kualitas rendah b / c pendek dan hanya url.
JasonB
Sebuah tutorial singkat yang bagus saya temukan: calleerlandsson.com/the-four-stages-of-compiling-ac-program
Guy Avraham