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.

c
#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):

text
Mode release (tanpa debug)
Fitur lama (versi 1)

Output yang Dihasilkan (kompilasi dengan gcc -std=c11 -Wall -Wextra -DDEBUG=1 -DVERSI=3):

text
[DEBUG] Mode debug aktif
Fitur baru diaktifkan (versi 3)

Output yang Dihasilkan (kompilasi dengan gcc -std=c11 -Wall -Wextra -DVERSI=2):

text
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.

c
#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=9 memicu error:
text
/tmp/test_elif.c:15:6: error: #error "PLATFORM tidak dikenali (harus 1, 2, atau 3)"

Mari kita telaah baris-baris kode di atas:

  1. Baris 4 (#ifndef PLATFORM): jika pengguna tidak menyuntikkan -DPLATFORM=N, kita belum memiliki macro PLATFORM. Directive #ifndef memilih blok di bawahnya untuk memberi nilai default.
  2. Baris 5 (#define PLATFORM 1): default nilai 1 = desktop. Praktik ini umum dipakai supaya kode tetap bisa di-build tanpa konfigurasi eksplisit.
  3. Baris 9 (#if PLATFORM == 1): ekspresi konstan. Jika PLATFORM adalah 1, kondisi bernilai true dan preprocessor memproses blok baris 10.
  4. Baris 11 (#elif PLATFORM == 2): jika #if di atas tidak terpenuhi, preprocessor mencoba #elif berikutnya. Cabang ini aktif untuk PLATFORM == 2.
  5. Baris 13 (#elif PLATFORM == 3): cabang ketiga untuk konfigurasi embedded.
  6. Baris 15 (#error "...")): jika semua cabang di atas gagal, #error menghentikan kompilasi dengan pesan yang kita tulis. Ini adalah fail-safe yang mencegah build berlanjut dengan konfigurasi yang tidak didukung.
  7. Baris 16 (#endif): menutup blok #if/#elif/#else yang 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:

text
#if defined(DEBUG) && !defined(RELEASE)
    /* kode debug-only */
#endif

Pola 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.