Episode ini membahas preprocessor: #include, #define, #ifdef, dan #pragma, lalu macro function dan konstanta yang aman, penyusunan Makefile dasar dengan rules dan targets, serta pendahuluan CMake untuk project yang portabel lintas platform.

Preprocessor adalah tahap pertama dari pipeline kompilasi yang kalian pelajari di episode 2. Semua yang diawali tanda # diproses sebelum compiler melihat kode. Episode 10 membahas direktif preprocessor secara menyeluruh dan bagaimana menggunakannya secara aman.
Di paruh kedua, kalian belajar build system: make dengan Makefile untuk project sederhana, dan cmake untuk project besar yang harus portabel di banyak platform. Build system mengubah kompilasi dari perintah manual berulang menjadi satu perintah yang terotomasi.
Setelah episode ini, project C kalian akan memiliki fondasi build yang rapi dan bisa dijalankan orang lain dengan satu perintah.
Direktif #include menyisipkan isi file ke dalam translation unit. Sudut untuk library standar, tanda kutip untuk header lokal project. Direktif #define mendefinisikan macro, yang sering dipakai sebagai konstanta:
#include <stdio.h>
#define VERSI 3
#define NAMA_PROGRAM "tool-konversi"
int main(void) {
printf("%s versi %d\n", NAMA_PROGRAM, VERSI);
return 0;
}Direktif #define VERSI 3 mengganti setiap kemunculan VERSI dengan 3 sebelum kompilasi. Berbeda dengan konstanta const, macro murni adalah penggantian teks tanpa tipe dan tanpa lokasi memori.
Preprocessor bisa menyaring blok kode berdasarkan kondisi. Pola #ifdef dipakai untuk conditional compilation, sedangkan #pragma once menyederhanakan include guard:
#ifdef DEBUG
printf("mode debug\n");
#endifJika DEBUG terdefinisi saat kompilasi, blok di atas ikut terkompilasi. Aktifkan lewat bendera -D:
gcc -DDEBUG program.c -o programBendera -DDEBUG setara dengan menaruh #define DEBUG di awal file. Teknik ini menjadi dasar conditional compilation lintas platform yang akan dibahas di episode 20.
Macro function berbahaya karena menggantikan teks mentah. Macro #define KUADRAT(x) x * x akan salah menghitung KUADRAT(2 + 3) menjadi 2 + 3 * 2 + 3. Solusinya adalah membungkus parameter dan hasil dengan tanda kurung:
#define KUADRAT(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))Pola #define MAX(a, b) ((a) > (b) ? (a) : (b)) menutup setiap parameter dan seluruh ekspresi dengan kurung. Meski begitu, macro yang dievaluasi dua kali bisa menggandakan efek samping. Untuk logika yang kompleks, lebih aman memakai fungsi static inline.
Untuk konstanta bertipe, gunakan const atau enum daripada #define. Untuk komputasi, gunakan fungsi static inline yang kecepatannya setara macro tetapi tetap mematuhi aturan tipe. Simpan #define untuk hal yang memang harus diketahui preprocessor, seperti include guard dan flag conditional compilation.
Makefile menyusun build sebagai aturan berformat target, dependencies, dan perintah. Baris perintah harus diawali karakter tab:
CC = gcc
CFLAGS = -Wall -Wextra -g
app: main.c math_utils.c
$(CC) $(CFLAGS) main.c math_utils.c -o app
clean:
rm -f app
.PHONY: cleanTarget app bergantung pada dua file sumber; jika salah satu berubah, make menjalankan perintahnya lagi. Target clean menghapus hasil build, dan .PHONY memberi tahu make bahwa clean bukan nama file.
Jalankan seluruh build dengan satu perintah:
make
./appmake membaca Makefile, memeriksa waktu modifikasi file, dan mengompilasi hanya yang berubah. Ini alasan utama memakai build system: membangun ulang project besar dari nol cukup sekali, dan perubahan kecil hanya mengompilasi file yang terkena dampak.
Makefile ditulis untuk satu platform. CMake adalah meta-build system: kalian menulis satu CMakeLists.txt, dan CMake menghasilkan Makefile, Ninja, atau project IDE sesuai platform tujuan. Ini membuat project portabel ke Linux, macOS, dan Windows.
cmake_minimum_required(VERSION 3.20)
project(tool_konversi LANGUAGES C)
add_executable(app main.c math_utils.c)
target_compile_options(app PRIVATE -Wall -Wextra)File CMakeLists.txt di atas mendeklarasikan executable bernama app dari dua sumber dan menambahkan opsi peringatan. Sintaksnya sederhana: add_executable menentukan binary, dan target_compile_options menempelkan bendera ke target spesifik.
CMake dipakai dua langkah: konfigurasi lalu build:
cmake -S . -B build
cmake --build build
./build/appPerintah cmake -S . -B build mengonfigurasi project ke direktori build, dan cmake --build build mengkompilasinya. Semua file yang dihasilkan tersimpan di folder build, menjaga direktori sumber tetap bersih — praktik terbaik yang akan kita gunakan lagi di episode 19.
Tip
Mulai dengan make untuk project kecil dan pindah ke CMake begitu project punya banyak target atau harus dikompilasi di banyak platform. CMake dapat menghasilkan Makefile, sehingga kedua alur saling melengkapi.
Inti yang harus dibawa pulang:
Di episode 11 selanjutnya kita akan membahas error handling dan debugging — error propagation dengan return code dan errno, diagnostik dengan assert dan logging, debugging dengan GDB dan LLDB memakai breakpoint, backtrace, dan watchpoint, serta static analysis tools seperti clang-tidy, cppcheck, dan sanitizers.