Directive #define adalah cara kita memberi nama simbolik pada nilai konstan atau potongan kode yang akan disisipkan oleh preprocessor. Sering disebut macro, fasilitas ini membuat kode lebih mudah dibaca dan dipelihara karena angka-angka ajaib seperti 3.14159 atau 100 dapat diberi nama yang bermakna. Directive pasangannya, #undef, menghapus definisi macro sehingga nama tersebut bebas dipakai ulang—termasuk untuk redefinisi dengan nilai berbeda. Pada chapter ini kita akan melihat cara kerja kedua directive, lingkup macro dari titik definisi sampai titik #undef, dan praktik mengganti konstanta numerik dengan nama simbolik.

Macro Object-Like untuk Konstanta #

Macro object-like adalah bentuk paling sederhana: nama macro diikuti nilai tanpa tanda kurung parameter. Saat preprocessor menjumpai nama macro di kode berikutnya, preprocessor menggantinya dengan nilai yang tercantum pada #define. Karena sekadar substitusi teks, macro dapat berupa angka, string, ekspresi, atau bahkan cuplikan kode C.

c
#include <stdio.h>

#define PI 3.14
#define NAMA "Demo Undef"

int main(void) {
    /* PI masih aktif di sini */
    printf("PI = %.2f, NAMA = %s\n", PI, NAMA);

    /* Hapus macro PI mulai titik ini */
#undef PI

    /* Re-define dengan presisi lebih tinggi */
#define PI 3.14159265

    printf("PI (redefinisi) = %.8f\n", PI);

    return 0;
}

Output yang Dihasilkan:

text
PI = 3.14, NAMA = Demo Undef
PI (redefinisi) = 3.14159265

Perhatikan bahwa PI di baris pertama printf menghasilkan 3.14 sesuai definisi awal, sedangkan PI di baris kedua menghasilkan 3.14159265 sesuai redefinisi setelah #undef. Tanpa #undef, redefinisi macro dengan nilai yang berbeda akan memicu peringatan -Wmacro-redefined dari GCC. #undef membersihkan definisi lama sehingga redefinisi berjalan tanpa warning.

Lingkup Macro: Dari #define Sampai #undef #

Macro bersifat global dalam satu translation unit sejak titik #define ditulis. Macro tidak mengenal block scope seperti variabel; ia aktif dari titik definisi sampai akhir file, atau sampai ditemui #undef yang menghapusnya. Karena itu penempatan #define di tengah fungsi valid secara sintaks—walaupun secara gaya lebih baik menempatkan semua #define di puncak file.

c
#include <stdio.h>

#define AKTIF 1

int main(void) {
    /* Uji apakah macro sudah didefinisikan */
#ifdef AKTIF
    printf("AKTIF didefinisikan, nilai = %d\n", AKTIF);
#else
    printf("AKTIF tidak didefinisikan\n");
#endif

    /* Hapus macro */
#undef AKTIF

#ifdef AKTIF
    printf("AKTIF masih ada\n");
#else
    printf("AKTIF sudah di-undef, tidak dapat dipakai lagi\n");
#endif

    return 0;
}

Output yang Dihasilkan:

text
AKTIF didefinisikan, nilai = 1
AKTIF sudah di-undef, tidak dapat dipakai lagi

Mari kita telaah baris-baris kode di atas:

  1. Baris 3 (#define AKTIF 1): mendefinisikan macro AKTIF dengan nilai 1. Mulai titik ini, setiap AKTIF yang muncul di kode akan diganti dengan 1.
  2. Baris 7 (#ifdef AKTIF): directive conditional compilation. Jika AKTIF sudah didefinisikan (yang benar pada titik ini), preprocessor memproses blok di bawahnya hingga #else.
  3. Baris 8 (printf("AKTIF didefinisikan, nilai = %d\n", AKTIF);): setelah preprocessing, AKTIF di argumen printf diganti dengan 1. Baris ini tetap ada pada translation unit hasil preprocessing.
  4. Baris 13 (#undef AKTIF): menghapus definisi AKTIF. Sejak titik ini, nama AKTIF tidak lagi dikenal preprocessor.
  5. Baris 15 (#ifdef AKTIF): pemeriksaan kedua. Karena AKTIF sudah di-undef, kondisi bernilai salah, sehingga preprocessor memilih cabang #else dan menyisipkan baris 18 ke translation unit.
  6. Baris 18 (printf("AKTIF sudah di-undef, ...");): satu-satunya baris di blok #ifdef kedua yang bertahan setelah preprocessing.

Praktik: Mengganti Magic Number #

Salah satu manfaat utama #define adalah menghilangkan magic number—angka literal yang muncul di tengah kode tanpa konteks. Memusatkan konstanta di satu tempat membuat perubahan nilai cukup dilakukan sekali, dan kode jauh lebih mudah dibaca.

c
#include <stdio.h>

#define UKURAN_ARRAY 5
#define BATAS_ATAS  100
#define BATAS_BAWAH 0

int main(void) {
    int data[UKURAN_ARRAY] = {10, 25, 50, 75, 90};

    for (int i = 0; i < UKURAN_ARRAY; i++) {
        if (data[i] >= BATAS_BAWAH && data[i] <= BATAS_ATAS) {
            printf("data[%d] = %d (valid)\n", i, data[i]);
        }
    }

    return 0;
}

Output yang Dihasilkan:

text
data[0] = 10 (valid)
data[1] = 25 (valid)
data[2] = 50 (valid)
data[3] = 75 (valid)
data[4] = 90 (valid)

Pada contoh di atas, UKURAN_ARRAY, BATAS_ATAS, dan BATAS_BAWAH adalah nama yang menjelaskan diri sendiri. Saat batas perlu diubah dari 100 menjadi 200, kita cukup mengedit satu baris #define BATAS_ATAS 200 dan semua referensi terupdate otomatis oleh preprocessor.