Separate compilation adalah praktik mengompilasi setiap file .c menjadi object file terpisah, lalu menyatukannya pada tahap linking menjadi satu executable. Pendekatan ini bukan sekadar gaya—pola ini mempercepat build ulang pada proyek besar karena perubahan di satu file .c hanya memicu kompilasi ulang file itu saja, bukan seluruh proyek. Selain itu, object file dapat dikemas menjadi static library (.a) untuk didistribusikan tanpa membocorkan kode sumber. Pada chapter ini kita akan mempraktikkan alur kerja tiga langkah—kompilasi .c ke .o, link beberapa .o menjadi executable, dan pembuatan static library libmath.a.
Alur Kerja Separate Compilation #
Pipeline build proyek C berskala menengah biasanya terdiri dari tiga langkah berulang. Pertama, setiap file .c dikompilasi terpisah menjadi object file .o dengan flag -c. Kedua, object file di-link menjadi satu executable (atau shared library). Ketiga, jika beberapa object file digunakan bersama di banyak program, kita dapat mengarsipkannya menjadi static library lib<nama>.a dengan tool ar.
Mekanisme di balik layar: setiap translation unit (file .c) dikompilasi tanpa perlu tahu isi file .c lain—cukup melihat deklarasi di header. Hasil kompilasi adalah object file yang berisi kode mesin dan tabel simbol (symbol table). Linker kemudian mencocokkan simbol yang belum terdefinisi (mis. pemanggilan faktorial dari main.o) dengan simbol yang didefinisikan di object file lain (mis. faktorial di math_utils.o). Jika semua simbol cocok, executable dihasilkan; jika tidak, linker melaporkan undefined reference.
Visualisasi Konsep #
Diagram berikut memperlihatkan alur separate compilation untuk proyek dengan dua file .c yang berbagi satu header:
Pada diagram di atas, header math_utils.h berisi deklarasi fungsi yang dipakai oleh kedua file .c melalui #include. Setiap file .c dikompilasi terpisah menjadi object file .o: math_utils.o berisi definisi fungsi (T faktorial, T fibonacci) sedangkan main.o hanya memakai fungsi tersebut (U faktorial, U fibonacci = undefined). Linker menyatukan kedua object file dengan mencocokkan simbol U di main.o dengan simbol T di math_utils.o; bila ada simbol yang tidak tercov, linker melaporkan undefined reference. Karena setiap file .c dikompilasi terpisah, perubahan pada math_utils.c hanya memicu kompilasi ulang file itu saja—bukan seluruh proyek.
Demonstrasi: Tiga Langkah Build #
Mari kita bangun proyek kecil dengan dua file .c: math_utils.c (modul) dan main.c (entry point). Keduanya berbagi header math_utils.h.
File math_utils.h:
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int faktorial(int n);
int fibonacci(int n);
#endifFile math_utils.c:
#include "math_utils.h"
int faktorial(int n) {
int hasil = 1;
for (int i = 2; i <= n; i++) {
hasil *= i;
}
return hasil;
}
int fibonacci(int n) {
if (n <= 1) return n;
int a = 0, b = 1;
for (int i = 2; i <= n; i++) {
int tmp = a + b;
a = b;
b = tmp;
}
return b;
}File main.c:
#include <stdio.h>
#include "math_utils.h"
int main(void) {
printf("5! = %d\n", faktorial(5));
printf("fib(10) = %d\n", fibonacci(10));
return 0;
}Langkah 1 — kompilasi math_utils.c menjadi object file:
gcc -std=c11 -Wall -Wextra -c math_utils.c -o math_utils.oFlag -c menyuruh GCC berhenti setelah kompilasi selesai, tanpa menjalankan linker. Hasilnya adalah file math_utils.o berisi kode mesin untuk fungsi faktorial dan fibonacci, lengkap dengan tabel simbol yang menyatakan kedua fungsi tersebut tersedia untuk linking.
Langkah 2 — kompilasi main.c sekaligus link dengan math_utils.o:
gcc -std=c11 -Wall -Wextra main.c math_utils.o -o appPada langkah ini, main.c dikompilasi menjadi translation unit lalu langsung di-link dengan math_utils.o. Linker mencari definisi faktorial dan fibonacci di math_utils.o dan menemukannya. Hasil akhir: executable app.
Langkah 3 — menjalankan executable:
./appOutput yang Dihasilkan:
5! = 120
fib(10) = 55Mari kita telaah baris-baris kunci di atas:
- Langkah 1, flag
-c: mengubah mode GCC dari “kompilasi+link” menjadi “kompilasi saja”. Tanpa-c, GCC akan langsung memanggil linker dan menghasilkan executable, bukan object file. - Langkah 1, output
math_utils.o: file biner berisi kode mesin untuk arsitektur target (mis. x86-64). File ini portabel pada arsitektur dan ABI yang sama—kita bisa memindahkannya ke mesin lain untuk di-link dengan program lain. - Langkah 2, daftar input
main.c math_utils.o: GCC mengkompilasimain.cmenjadi translation unit lalu menyuruh linker menyatukannya denganmath_utils.o. Urutan input tidak kritis untuk static linking, tetapi menjadi penting untuk shared library. - Langkah 2, flag
-o app: nama executable hasil. Tanpa-o, default adalaha.out. - Verifikasi simbol: untuk memeriksa simbol apa saja yang diekspor oleh
math_utils.o, jalankannm math_utils.o. Outputnya akan menunjukkan barisT faktorialdanT fibonacci, yang artinya kedua fungsi berada di text section dan terlihat dari luar (external linkage).
Membuat Static Library libmath.a #
Jika sebuah modul dipakai oleh banyak program, mengompilasi ulang modul untuk setiap program adalah pemborosan. Kita dapat mengarsipkan beberapa object file menjadi satu static library dengan tool ar (archiver).
Langkah 1 — kompilasi math_utils.c menjadi object file (sama seperti sebelumnya):
gcc -std=c11 -Wall -Wextra -c math_utils.c -o math_utils.oLangkah 2 — arsipkan menjadi static library libmath.a:
ar rcs libmath.a math_utils.oTiga flag ar di atas: r (replace/insert), c (create jika belum ada), s (write index/symbol table). Hasilnya adalah libmath.a—sebuah archive berisi math_utils.o.
Langkah 3 — kompilasi main.c dan link dengan libmath.a:
gcc -std=c11 -Wall -Wextra main.c -L. -lmath -o appFlag -L. menambahkan direktori saat ini ke library search path. Flag -lmath menyuruh linker mencari library bernama libmath.a (konvensi: lib<nama>.a + flag -l<nama>).
Output yang Dihasilkan:
3! = 6, fib(7) = 13