Saat kita mengetik g++ program.cpp -o program, compiler sebenarnya menjalankan empat tahap berurutan yang masing-masing menghasilkan keluaran berbeda. Memahami tahapan ini bukan sekadar teori: ketika kita menemui pesan undefined reference atau header file not found, mengetahui di tahap mana error terjadi mempercepat diagnosis. Pada Chapter ini kita akan menjalankan tiap tahap secara terpisah dengan flag -E, -S, dan -c, lalu menghubungkan beberapa file objek menjadi satu binary. Kita akan memakai proyek kecil dengan dua file .cpp dan satu header .hpp untuk mendemokan proses linking.
Empat Tahap Kompilasi #
Pipeline kompilasi C++ terdiri dari empat tahap:
Tabel berikut merangkum tiap tahap, perintah untuk menjalankannya secara terpisah, dan keluaran yang dihasilkan:
| Tahap | Alat | Perintah GCC | Keluaran |
|---|---|---|---|
| 1. Preprocessing | Preprocessor (cpp) | g++ -E file.cpp | Kode sumber dengan macro & include disubstitusi |
| 2. Kompilasi | Compiler (cc1plus) | g++ -S file.cpp | File assembly .s |
| 3. Assembling | Assembler (as) | g++ -c file.cpp | File objek .o |
| 4. Linking | Linker (ld) | g++ file1.o file2.o -o exe | Binary executable |
Struktur Proyek Demo #
Kita akan memakai tiga file berikut untuk mendemokan pipeline:
// File: math_utils.hpp
#ifndef MATH_UTILS_HPP
#define MATH_UTILS_HPP
int kuadrat(int x);
#endif// File: math_utils.cpp
#include "math_utils.hpp"
int kuadrat(int x) {
return x * x;
}// File: main_pipeline.cpp
#include <iostream>
#include "math_utils.hpp"
#define NAMA "Pipeline Demo"
int main() {
std::cout << NAMA << "\n";
std::cout << "Kuadrat 7 = " << kuadrat(7) << "\n";
return 0;
}Header guard #ifndef ... #define ... #endif pada math_utils.hpp mencegah double inclusion ketika header disertakan dari beberapa file .cpp sekaligus. Tanpa header guard, definisi kuadrat akan tercatat dua kali dan menyebabkan error saat linking.
Tahap 1: Preprocessing #
Preprocessor memproses semua direktif yang diawali #: #include disubstitusi dengan isi file yang ditunjuk, #define disubstitusi pada tempat pemakaiannya, #ifdef/#endif mengevaluasi kondisi kompilasi bersyarat.
Mekanisme #
Saat preprocessor menemui #include "math_utils.hpp", preprocessor membuka file math_utils.hpp lalu menempelkan isinya menggantikan baris #include. Saat menemui #define KUADRAT(x) ((x) * (x)), setiap pemanggilan KUADRAT(7) digantikan oleh ((7) * (7)). Preprocessor juga menyisipkan line markers berbentuk # angka "file" agar compiler dapat menelusuri lokasi asli tiap baris untuk pesan error.
Perintah dan Output #
Untuk melihat hasil preprocessing saja (tanpa kompilasi), pakai flag -E. Agar output lebih ringkas, kita pakai file demo kecil tanpa <iostream>:
// File: prepro_demo.cpp
#define NAMA "Pipeline Demo"
#define KUADRAT(x) ((x) * (x))
#include "math_utils.hpp"
int main() {
int hasil = KUADRAT(7);
return hasil + kuadrat(7);
}Jalankan preprocessor dan tampilkan bagian akhir output:
g++ -std=c++17 -E prepro_demo.cpp | tail -15Output yang Dihasilkan (hanya bagian akhir ditampilkan):
# 1 "math_utils.hpp" 1
int kuadrat(int x);
# 6 "prepro_demo.cpp" 2
int main() {
int hasil = ((7) * (7));
return hasil + kuadrat(7);
}Bedah Output #
- Baris
int kuadrat(int x);: ini berasal darimath_utils.hppyang disisipkan menggantikan#include "math_utils.hpp". Header guard membuat#ifndefpertama tidak mengulang definisi bila header diinclude ulang. - Baris
int hasil = ((7) * (7));: macroKUADRAT(7)digantikan oleh definisi((7) * (7)). Tanda kurung ganda mencegah precedence bug ketika argumen berupa ekspresi. # 1 "math_utils.hpp" 1dan# 6 "prepro_demo.cpp" 2: line markers yang dipakai compiler untuk melacak asal usul tiap baris. Inilah yang membuat pesan error dapat menyebut file header walaupun kompilasi dimulai dari file.cpp.
Tahap 2: Kompilasi ke Assembly #
Pada tahap kedua, compiler menerjemahkan translation unit (hasil preprocessing) menjadi kode assembly. Kode assembly adalah representasi teks yang mendekati instruksi mesin tetapi masih dapat dibaca manusia.
Perintah dan Output #
g++ -std=c++17 -S main_pipeline.cpp -o main_pipeline.sPerintah di atas menghasilkan file main_pipeline.s. Tampilkan beberapa baris pertama:
head -5 main_pipeline.sOutput yang Dihasilkan:
.file "main_pipeline.cpp"
.text
#APP
.globl _ZSt21ios_base_library_initv
.section .rodataBaris-baris di atas adalah instruksi assembly AT&T syntax. Simbol _ZSt21ios_base_library_initv adalah mangled name dari fungsi internal STL yang dipanggil saat inisialisasi. Name mangling mengkodekan informasi tipe ke dalam nama simbol agar fitur seperti function overloading dapat bekerja.
Tahap 3: Assembling ke Object Code #
Tahap ketiga, assembling, menerjemahkan kode assembly menjadi instruksi mesin biner. Hasilnya disebut object file dengan ekstensi .o (Linux/macOS) atau .obj (Windows). Object file belum bisa dijalankan karena referensi ke simbol di file lain belum diselesaikan.
Perintah dan Output #
g++ -std=c++17 -c main_pipeline.cpp -o main_pipeline.o
g++ -std=c++17 -c math_utils.cpp -o math_utils.oVerifikasi tipe file dengan perintah file:
file main_pipeline.oOutput yang Dihasilkan (di Linux 64-bit):
main_pipeline.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not strippedPenjelasan output:
- ELF: format file executable Linux (Executable and Linkable Format).
- 64-bit LSB: arsitektur 64-bit, least significant byte (little-endian).
- relocatable: alamat simbol belum final, akan diselesaikan saat linking.
- x86-64: target arsitektur CPU.
- not stripped: masih mengandung simbol debug.
Tahap 4: Linking #
Tahap terakhir, linking, menggabungkan beberapa object file dan library menjadi satu binary executable. Linker menyelesaikan referensi simbol antar file: misalnya, main_pipeline.o memanggil kuadrat(int) yang didefinisikan di math_utils.o. Linker mencocokkan kedua simbol tersebut dan menghasilkan binary yang dapat dijalankan.
Perintah dan Output #
g++ -std=c++17 main_pipeline.o math_utils.o -o pipeline_demo
./pipeline_demoOutput yang Dihasilkan:
Pipeline Demo
Kuadrat 7 = 49Bedah Proses Linking #
main_pipeline.oberisi fungsimainyang memanggilkuadrat(7). Pada saat compilingmain_pipeline.cpp, compiler tidak tahu implementasikuadrat— compiler hanya tahu deklarasi darimath_utils.hpp. Alamatkuadratsementara dibiarkan kosong di object file.math_utils.oberisi definisikuadrat. Object file ini menyediakan alamat fungsikuadrat.- Saat linking, linker membaca kedua file objek, mencocokkan referensi
kuadratdimain_pipeline.odengan definisi dimath_utils.o, lalu mengisi alamat akhir. - Setelah seluruh simbol terkait, linker menambahkan runtime startup code yang memanggil
maindari sistem operasi dan menghasilkan binarypipeline_demo.
Pesan Error Umum Tiap Tahap #
Memahami tahap kompilasi membantu kita memetakan pesan error:
| Tahap | Pesan Error Khas | Penyebab Umum |
|---|---|---|
| Preprocessing | fatal error: foo.hpp: No such file or directory | Path include salah atau file header tidak ada |
| Kompilasi | error: 'X' was not declared in this scope | Variabel/fungsi belum dideklarasikan |
| Kompilasi | error: no matching function for call to ... | Tipe argumen tidak cocok dengan overload |
| Assembling | Jarang error; biasanya masalah hardware/disk | File .s korup |
| Linking | undefined reference to 'kuadrat(int)' | Definisi fungsi tidak ditemukan saat linking |
| Linking | multiple definition of 'X' | Fungsi/variabel didefinisikan di header tanpa inline |
Memeriksa Simbol di Object File #
Untuk melihat simbol apa saja yang ada di file objek, kita bisa memakai nm:
nm math_utils.oOutput yang Dihasilkan (disederhanakan):
0000000000000000 T _Z7kuadratiPenjelasan:
Tdi kolom kedua berarti simbol berada di section.text(kode, global)._Z7kuadratiadalah mangled name. Dekode:_Z= awal,7= panjang nama berikutnya,kuadrat= nama fungsi,i= tipe parameterint. Compiler demangle:kuadrat(int).
Perintah c++filt mendekode mangled name ke bentuk asli:
nm math_utils.o | c++filtOutput yang Dihasilkan:
0000000000000000 T kuadrat(int)Ringkasan #
Pada Chapter ini kita telah membedah empat tahap kompilasi C++:
- Preprocessing (
-E): substitusi#include,#define, dan#ifdef. - Kompilasi (
-S): menerjemahkan translation unit menjadi kode assembly. - Assembling (
-c): menerjemahkan assembly menjadi object file.o. - Linking: menggabungkan object file dan library menjadi binary executable.
Pemahaman ini akan membantu kita men-debug error undefined reference, masalah header guard, dan kebingungan seputar translation unit. Pada Chapter berikutnya kita akan memakai CMake untuk mengotomatiskan proses build pada proyek multi-file.