Saat kita mengetik gcc main.c -o main, GCC sebenarnya menjalankan empat tahap berurutan: preprocessing, compilation, assembly, dan linking. Setiap tahap memiliki masalah khasnya sendiri — kesalahan makro muncul di preprocessing, kesalahan sintaks di compilation, dan undefined reference di linking. Pada Chapter ini kita akan menjalankan setiap tahap secara terpisah, memeriksa output antara, dan melihat apa yang sebenarnya dihasilkan oleh GCC pada masing-masing tahap.
Empat Tahap Kompilasi #
Tahapan yang berjalan saat GCC memproses file .c adalah:
- Preprocessing — memproses direktif
#include,#define,#if, dan lainnya; menghasilkan kode C murni tanpa direktif. - Compilation — menerjemahkan kode C hasil preprocess menjadi kode assembly spesifik arsitektur.
- Assembly — menerjemahkan kode assembly menjadi kode mesin; menghasilkan object file
.o. - Linking — menggabungkan satu atau lebih object file beserta pustaka standar menjadi executable.
GCC menyediakan flag untuk menghentikan proses pada setiap tahap agar kita bisa memeriksa hasil antara.
Visualisasi Konsep #
Diagram berikut memetakan empat tahap kompilasi GCC beserta file antara yang dihasilkan pada setiap tahap:
Pada diagram di atas, file main.c mengalir melalui empat tahap berurutan: preprocessing (mengolah #include dan #define), compilation (menerjemahkan C menjadi assembly), assembly (menerjemahkan assembly menjadi object file kode mesin), dan linking (menggabungkan object file dengan pustaka standar menjadi executable). Setiap tahap menghasilkan file antara yang bisa diperiksa dengan flag -E, -S, dan -c. Pesan galat yang muncul pada tahap berbeda menunjukkan masalah berbeda: preprocessor error untuk direktif #, compiler error untuk sintaks C, dan linker error (undefined reference) untuk simbol tak terdefinisi.
Tahap Preprocessing #
Flag -E menyuruh GCC berhenti setelah preprocessing. Output ditulis ke standard output; biasanya kita piping ke tail agar terbaca. Mari kita lihat kode sumber berikut:
#include <stdio.h>
#define NAMA "Andi"
#define UBAH 5
int main(void) {
int nilai = 10 + UBAH;
printf("Halo, %s! Nilai = %d\n", NAMA, nilai);
return 0;
}Periksa hasil preprocessing dengan perintah:
gcc -std=c11 -Wall -Wextra -E test_ch11_a.c | tail -6Output yang Dihasilkan:
# 6 "/tmp/test_ch11_a.c"
int main(void) {
int nilai = 10 + 5;
printf("Halo, %s! Nilai = %d\n", "Andi", nilai);
return 0;
}Mari kita telaah baris-baris output di atas:
- Baris 1 (
# 6 "/tmp/test_ch11_a.c"): penanda baris (line marker) yang dipakai compiler untuk melacak baris asli kode sumber. Format# N "file"menyatakan baris berikutnya berasal dari file tersebut pada barisN. - Baris 3 (
int nilai = 10 + 5;): makroUBAHsudah diganti dengan5; compiler sekarang melihat ekspresi aritmetika10 + 5. - Baris 4 (
printf("Halo, %s! ...", "Andi", nilai);): makroNAMAsudah diganti dengan string literal"Andi".
Tahap Compilation ke Assembly #
Flag -S menyuruh GCC berhenti setelah compilation, menghasilkan file assembly .s. Mari kita lihat kode assembly yang dihasilkan:
gcc -std=c11 -Wall -Wextra -S test_ch11_a.c -o test_ch11_a.s
head -15 test_ch11_a.sOutput yang Dihasilkan:
.file "test_ch11_a.c"
.text
.section .rodata
.LC0:
.string "Andi"
.LC1:
.string "Halo, %s! Nilai = %d\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16Mari kita telaah baris-baris output assembly di atas:
- Baris 1 (
.file "test_ch11_a.c"): direktif assembler yang menyatakan nama file sumber. - Baris 4 (
.LC0:): label untuk string"Andi"di section.rodata(data hanya baca). - Baris 6 (
.LC1:): label untuk string formatprintf. - Baris 8 (
.globl main): menandai simbolmainsebagai global agar bisa dipanggil oleh startup code sistem operasi. - Baris 10 (
main:): titik masuk fungsimaindi tingkat assembly. - Baris 13 (
pushq %rbp): instruksi prolog fungsi yang menyimpan base pointer di stack — pola standar pemanggilan fungsi pada arsitektur x86-64.
Tahap Assembly ke Object File #
Flag -c menyuruh GCC berhenti setelah assembly, menghasilkan object file .o berisi kode mesin namun belum siap dijalankan:
gcc -std=c11 -Wall -Wextra -c test_ch11_a.c -o test_ch11_a.o
file test_ch11_a.o
ls -l test_ch11_a.oOutput yang Dihasilkan:
test_ch11_a.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
-rw-rw-r-- 1 z z 1448 Oct 13 03:46 test_ch11_a.oPerintah file mengonfirmasi format object file ini: ELF relocatable 64-bit untuk arsitektur x86-64. Kata relocatable berarti alamat-alamat simbol di dalamnya belum ditentukan; tugas linker adalah memfinalisasi alamat tersebut.
Kita bisa memeriksa simbol apa saja yang ada di object file dengan nm:
nm test_ch11_a.oOutput yang Dihasilkan:
0000000000000000 T main
U printfMari kita telaah baris-baris output di atas:
- Baris 1 (
T main): hurufTberarti simbol berada di section text (kode) dan sudah terdefinisi di object file ini. - Baris 2 (
U printf): hurufUberarti simbol undefined — object file ini memakaiprintftetapi tidak mendefinisikannya; linker harus mencari definisiprintfdi pustaka standar.
Tahap Linking #
Tahap terakhir adalah linking. Pada tahap ini, linker menggabungkan satu atau lebih object file beserta pustaka standar (seperti libc) menjadi executable. Pustaka standar berisi definisi printf dan fungsi-fungsi lain yang dipakai runtime C.
gcc -std=c11 -o test_ch11_a test_ch11_a.o
./test_ch11_aOutput yang Dihasilkan:
Halo, Andi! Nilai = 15Linker mengambil object file test_ch11_a.o yang berisi simbol main dan printf (belum terdefinisi), lalu mencari definisi printf di pustaka standar libc. Setelah simbol-simbol diresolusi, executable akhir test_ch11_a siap dijalankan.
Melakukan Keempat Tahap Satu Perintah #
Dalam praktik sehari-hari, kita jarang memisahkan keempat tahap secara eksplisit. Satu perintah gcc saja sudah cukup:
gcc -std=c11 -Wall -Wextra -o program main.cGCC secara internal menjalankan preprocessor cpp, compiler cc1, assembler as, dan linker ld secara berurutan. Memahami tahap-tahap ini tetap penting karena:
- Pesan galat seperti
undefined reference to 'printf'menandakan masalah di linking. - Pesan galat seperti
expected ';' before '}' tokenmenandakan masalah di compilation. - Pesan galat seperti
#include expects "FILENAME" or <FILENAME>menandakan masalah di preprocessing.
- Saat men-debug kompilasi, gunakan flag
-v(verbose) untuk melihat keempat tahap yang berjalan:gcc -v -std=c11 -o program main.c. Output akan menampilkan setiap sub-program yang dipanggil beserta argumennya. - Pesan galat undefined reference yang melibatkan fungsi pustaka standar seperti
printfatausqrtbiasanya berarti kita lupa menyertakan pustaka saat linking. Untukprintf, pustakalibcdi-link otomatis oleh GCC. Untuksqrtdari header<math.h>, kita perlu menambahkan flag-lmdi akhir perintahgcc.