Satu panggilan gcc sebenarnya menjalankan empat tahap berbeda — preprocessor, compiler, assembler, lalu linker — yang masing-masing menghasilkan file antara berbeda. Memahami tahapan ini penting karena saat kompilasi gagal, pesan error menyebut tahap mana yang bersalah. Pada Chapter ini kita menjalankan keempat tahap secara manual, membedakan gcc dari g++, lalu belajar membaca dua keluarga pesan error yang paling sering menghentikan build.

Empat Tahap di Balik Satu Perintah #

Empat tahap kompilasi bekerja seperti jalur perakitan: tiap tahap menerima hasil tahap sebelumnya, dan driver gcc menyediakan satu opsi untuk berhenti di tiap tahap:

TahapProgram kerjaHasilOpsi penghenti
Preprocessingpreprocessorhalo.i — header dan macro terurai-E
Compilationcompilerhalo.s — kode assembly-S
Assemblyassemblerhalo.o — object file ELF-c
Linkinglinkerhalo — executable(default)

Ekstensi .i, .s, dan .o adalah konvensi driver; tahap pertama menerapkan semua arahan # sehingga hasilnya C murni tanpa arahan preprocessor.

Menyusuri Empat Tahap Secara Manual #

Kita pakai program kecil berisi satu #define agar kerja preprocessor terlihat; tiap langkah berhenti di satu tahap lalu memeriksa hasilnya:

bash
cat > halo.c << 'AKHIR'
#include <stdio.h>

#define NAMA "Budi"

int main(void)
{
    printf("Halo, %s!\n", NAMA);
    return 0;
}
AKHIR
gcc -E halo.c | wc -l
gcc -S halo.c
grep -n "main:" halo.s
gcc -c halo.c
file halo.o
gcc -o halo halo.o
./halo

Output yang Dihasilkan:

text
819
11:main:
halo.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
Halo, Budi!

Bedah tiap langkah dan hasilnya:

  1. gcc -E halo.c | wc -l: preprocessing saja — #include diganti isi stdio.h beserta rantai header-nya, dan NAMA diganti "Budi". Hasilnya ratusan baris di mesin uji; angkanya berbeda antar distro, jadi wc -l dipakai alih-alih menampilkan semuanya.
  2. gcc -S halo.c lalu grep -n "main:" halo.s: kompilasi ke assembly — halo.s berisi instruksi x86-64 yang masih terbaca manusia; grep menemukan label main di baris 11 (bisa bergeser antar versi compiler).
  3. gcc -c halo.c lalu file halo.o: assembly menjadi object file — kode mesin dalam ELF, tetapi masih relocatable: alamat belum final karena belum digabung.
  4. gcc -o halo halo.o: linking — linker merangkai halo.o dengan startup code dan pustaka lalu menyelesaikan semua alamat. Output ./halo membuktikan NAMA sudah tergantikan "Budi" sejak tahap pertama.

gcc versus g++ #

gcc dan g++ adalah dua pintu masuk ke compiler yang sama, tetapi g++ memperlakukan file sebagai C++ dan menautkan pustaka standar C++ (libstdc++) saat linking. Nama C++ juga di-mangling — tipe dikodekan ke nama simbol, misalnya std::cout menjadi _ZSt4cout. Perbedaan kecil ini melahirkan jebakan klasik:

bash
cat > halo.cpp << 'AKHIR'
#include <iostream>

int main()
{
    std::cout << "Halo dari C++!\n";
    return 0;
}
AKHIR
g++ -Wall -o halo_pp halo.cpp
./halo_pp
gcc -c halo.cpp
gcc -o salah halo.o 2>&1

Output yang Dihasilkan:

text
Halo dari C++!
/usr/bin/ld: halo.o: warning: relocation against `_ZSt4cout' in read-only section `.text'
/usr/bin/ld: halo.o: in function `main':
halo.cpp:(.text+0x11): undefined reference to `std::cout'
/usr/bin/ld: halo.cpp:(.text+0x19): undefined reference to `std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status

Apa yang terjadi pada blok di atas:

  1. g++ -Wall -o halo_pp halo.cpp dan ./halo_pp: jalur yang benar — libstdc++ ikut ditautkan dan program berjalan lancar.
  2. gcc -c halo.cpp: tahap kompilasi tetap berhasil — penerjemah C++ di dalam gcc ikut dipanggil untuk file .cpp.
  3. gcc -o salah halo.o 2>&1: jebakannya — tahap link gagal karena gcc tidak menautkan libstdc++, padahal object file butuh definisi std::cout dari pustaka itu.

Opsi yang Paling Sering Dipakai #

Driver gcc punya ratusan opsi, tetapi sekelompok kecil menyumbang mayoritas pemakaian sehari-hari:

OpsiPeran
-o namamenamai file hasil
-Wallmengaktifkan kelompok warning paling berguna
-O2optimalkan kecepatan (huruf O besar, bukan nol)
-gsertakan info debug untuk gdb
-Idirtambah lokasi pencarian header
-Ldirtambah lokasi pencarian pustaka
-lnamatautkan pustaka libnama.so/libnama.a

Tiga opsi terakhir bekerja sebagai satu tim dan paling baik dipahami lewat praktik — contoh berikut sekaligus memakai -Wall -O2, menaruh header di inc/, mengemas fungsi ke pustaka statis dengan ar, lalu menautkannya:

bash
mkdir -p inc src
cat > inc/sapa.h << 'AKHIR'
int sapa(int n);
AKHIR
cat > src/sapa.c << 'AKHIR'
#include "sapa.h"

int sapa(int n)
{
    return n + 1;
}
AKHIR
cat > utama.c << 'AKHIR'
#include <stdio.h>
#include "sapa.h"

int main(void)
{
    printf("hasil: %d\n", sapa(40));
    return 0;
}
AKHIR
gcc -Wall -O2 -Iinc -c src/sapa.c -o sapa.o
ar rcs libsapa.a sapa.o
gcc -Wall -O2 -Iinc -c utama.c -o utama.o
gcc -o program utama.o -L. -lsapa
./program

Output yang Dihasilkan:

text
hasil: 41

Bedah opsi-opsi yang bekerja di balik layar:

  1. -Iinc: menambah inc/ ke pencarian header sehingga #include "sapa.h" ketemu walau tidak sefolder — tanpanya compiler keluar dengan fatal error: sapa.h: No such file or directory.
  2. ar rcs libsapa.a sapa.o: archiver ar menggabungkan object file menjadi arsip pustaka statis dengan konvensi lib + nama + .a — justru nama tengahnya, sapa, yang dipakai opsi berikutnya.
  3. -L. -lsapa: -L. menambah direktori kerja ke pencarian pustaka, lalu -lsapa memerintahkan linker mencari libsapa.a/libsapa.so. Karena yang ada versi statis, kode sapa.o disalin langsung ke dalam program.

Pustaka nyata seperti libssl ditautkan dengan pola yang sama; di proyek besar pkg-config yang menghitung opsi ini — materi Chapter pkg-config dan Linking di akhir section ini.

Membaca Pesan Error Kompilasi #

Dua keluarga error mendominasi build C/C++. Keluarga pertama berhenti di tahap kompilasi dengan format file:baris:kolom:

bash
printf '#include <tidakada.h>\nint main(void)\n{\n    return 0;\n}\n' > gagal.c
gcc -o gagal gagal.c 2>&1

Output yang Dihasilkan:

text
gagal.c:1:10: fatal error: tidakada.h: No such file or directory
    1 | #include <tidakada.h>
      |          ^~~~~~~~~~~~
compilation terminated.

Pesan fatal error: tidakada.h: No such file or directory berarti preprocessor gagal menemukan header — biasanya salah ketik atau package -dev pemiliknya belum terpasang. Keluarga kedua berhenti saat link dengan format undefined reference, seperti pada halo.cpp: simbol dipanggil tapi definisinya tidak ditemukan — penyebab klasiknya fungsi yang lupa didefinisikan atau opsi -l yang hilang. Keduanya selalu menuliskan nama file plus lokasi persis; membaca pesan dari baris pertama menghemat waktu lebih banyak daripada hafalan opsi. Rujukan lengkap: gcc(1) , g++(1) , dan ld(1) .

Empat tahap dan dua keluarga error di atas adalah wajah dunia bahasa compiled. Bab berikutnya berpindah model: Python, yang source-nya dibaca langsung interpreter tanpa kompilasi manual — dan justru karena itu memunculkan persoalan versi serta dependensi yang khas.