Conditional compilation memungkinkan kita menyertakan atau membuang bagian kode sumber berdasarkan kondisi yang dievaluasi oleh preprocessor. Fasilitas ini dipakai untuk banyak skenario: mengaktifkan kode debug saat pengembangan namun mematikannya di release build, mendukung banyak platform dalam satu codebase, atau memilih fitur yang aktif berdasarkan versi pustaka. Tanpa conditional compilation, kita akan terpaksa memelihara beberapa branch kode yang saling mirip hanya untuk perbedaan kecil. Pada chapter ini kita akan mempelajari directive #ifdef, #if, #elif, #else, #endif, dan #error, serta cara menginjeksikan macro dari command line via -D.
Directive #ifdef dan #ifndef #
Directive #ifdef NAMA memeriksa apakah macro NAMA sudah didefinisikan—tanpa peduli nilainya berapa. Jika sudah didefinisikan, preprocessor memproses blok di bawah #ifdef sampai #else atau #endif. #ifndef adalah kebalikannya: blok diproses jika macro belum didefinisikan. Pola kedua ini sangat umum dipakai untuk include guard yang akan kita bahas di chapter khusus.
#include <stdio.h>
/* Default VERSI jika tidak didefinisikan via -D */
#ifndef VERSI
#define VERSI 1
#endif
int main(void) {
#ifdef DEBUG
printf("[DEBUG] Mode debug aktif\n");
#else
printf("Mode release (tanpa debug)\n");
#endif
#if VERSI >= 2
printf("Fitur baru diaktifkan (versi %d)\n", VERSI);
#else
printf("Fitur lama (versi %d)\n", VERSI);
#endif
return 0;
}Output yang Dihasilkan (kompilasi standar tanpa flag -D):
Mode release (tanpa debug)
Fitur lama (versi 1)Output yang Dihasilkan (kompilasi dengan gcc -std=c11 -Wall -Wextra -DDEBUG=1 -DVERSI=3):
[DEBUG] Mode debug aktif
Fitur baru diaktifkan (versi 3)Output yang Dihasilkan (kompilasi dengan gcc -std=c11 -Wall -Wextra -DVERSI=2):
Mode release (tanpa debug)
Fitur baru diaktifkan (versi 2)Perhatikan tiga skenario yang berbeda dihasilkan dari satu file sumber yang sama. Flag -DDEBUG=1 mendefinisikan macro DEBUG dengan nilai 1; preprocessor kemudian memilih blok #ifdef DEBUG yang aktif. Flag -DVERSI=3 mendefinisikan macro VERSI dengan nilai 3; preprocessor mengevaluasi #if VERSI >= 2 sebagai 3 >= 2 yang bernilai true, sehingga blok “Fitur baru” dipilih.
Directive #if, #elif, dan Ekspresi Konstan #
Directive #if menerima ekspresi konstan integer. Ekspresi ini di-evaluasi oleh preprocessor menggunakan aturan kalkulasi integer constant expression (standar ISO/IEC 9899 §6.10.1). Macro yang muncul di ekspresi di-expand terlebih dahulu; identifier yang tersisa (tidak didefinisikan sebagai macro) diperlakukan sebagai 0. Operator yang didukung meliputi aritmetika, perbandingan, logika, dan operator ternary.
Directive #elif adalah gabungan #else dan #if: jika cabang sebelumnya tidak terpilih, #elif mengevaluasi ekspresi barunya. Pola ini sangat berguna untuk memilih satu di antara banyak cabang—mis. memilih platform.
#include <stdio.h>
#ifndef PLATFORM
#define PLATFORM 1 /* default: 1 = desktop */
#endif
int main(void) {
#if PLATFORM == 1
printf("Build untuk Desktop (Windows/Linux/macOS)\n");
#elif PLATFORM == 2
printf("Build untuk Mobile (Android/iOS)\n");
#elif PLATFORM == 3
printf("Build untuk Embedded (ARM Cortex)\n");
#else
#error "PLATFORM tidak dikenali (harus 1, 2, atau 3)"
#endif
return 0;
}Output yang Dihasilkan untuk berbagai nilai PLATFORM (disuntik via -DPLATFORM=N):
gcc -DPLATFORM=1:Build untuk Desktop (Windows/Linux/macOS)gcc -DPLATFORM=2:Build untuk Mobile (Android/iOS)gcc -DPLATFORM=3:Build untuk Embedded (ARM Cortex)gcc -DPLATFORM=9memicu error:
/tmp/test_elif.c:15:6: error: #error "PLATFORM tidak dikenali (harus 1, 2, atau 3)"Mari kita telaah baris-baris kode di atas:
- Baris 4 (
#ifndef PLATFORM): jika pengguna tidak menyuntikkan-DPLATFORM=N, kita belum memiliki macroPLATFORM. Directive#ifndefmemilih blok di bawahnya untuk memberi nilai default. - Baris 5 (
#define PLATFORM 1): default nilai1= desktop. Praktik ini umum dipakai supaya kode tetap bisa di-build tanpa konfigurasi eksplisit. - Baris 9 (
#if PLATFORM == 1): ekspresi konstan. JikaPLATFORMadalah1, kondisi bernilai true dan preprocessor memproses blok baris 10. - Baris 11 (
#elif PLATFORM == 2): jika#ifdi atas tidak terpenuhi, preprocessor mencoba#elifberikutnya. Cabang ini aktif untukPLATFORM == 2. - Baris 13 (
#elif PLATFORM == 3): cabang ketiga untuk konfigurasi embedded. - Baris 15 (
#error "...")): jika semua cabang di atas gagal,#errormenghentikan kompilasi dengan pesan yang kita tulis. Ini adalah fail-safe yang mencegah build berlanjut dengan konfigurasi yang tidak didukung. - Baris 16 (
#endif): menutup blok#if/#elif/#elseyang dimulai di baris 9.
Operator defined dan Pola Gabungan #
Selain #ifdef, kita dapat memakai operator defined(NAME) di dalam #if. Operator ini mengembalikan 1 jika NAME sudah didefinisikan dan 0 jika belum. Keunggulan defined() adalah dapat dikombinasikan dengan operator logika lain dalam satu ekspresi:
#if defined(DEBUG) && !defined(RELEASE)
/* kode debug-only */
#endifPola ini sangat fleksibel untuk menggabungkan beberapa kondisi. defined(DEBUG) && !defined(RELEASE) hanya bernilai true jika macro DEBUG sudah didefinisikan dan macro RELEASE belum. Pola sejenis dipakai di banyak header pustaka standar untuk menyesuaikan deklarasi dengan fitur compiler yang aktif.