Header C sering di-include lebih dari sekali dalam satu translation unit—baik secara langsung maupun tidak langsung melalui header lain. Tanpa proteksi, multiple inclusion menyebabkan redefinition error untuk setiap tipe, fungsi, atau macro yang didefinisikan di header. Include guard adalah pola conditional compilation yang memastikan isi header hanya diproses sekali oleh preprocessor per translation unit. Pada chapter ini kita akan melihat apa yang terjadi tanpa include guard, bagaimana pola #ifndef/#define/#endif menyelesaikan masalah, dan praktik penamaan token guard yang aman dari tabrakan.
Masalah Multiple Inclusion #
Preprocessor melakukan substitusi #include dengan menyisipkan seluruh isi file header ke posisi directive. Jika sebuah header di-include dua kali, seluruh isinya akan muncul dua kali pada translation unit. Akibatnya, setiap definisi (tipe, fungsi inline, konstanta) yang muncul di header tersebut juga muncul dua kali—dan compiler menolak definisi ganda.
Mari kita lihat error yang muncul ketika header tanpa guard di-include dua kali:
File math_utils.h (tanpa guard):
#define PI 3.14159265
int kuadrat(int x) {
return x * x;
}File main.c:
#include <stdio.h>
#include "math_utils.h"
#include "math_utils.h" /* kedua: akan error redefinition */
int main(void) {
printf("kuadrat(5) = %d\n", kuadrat(5));
return 0;
}Output yang Dihasilkan saat kompilasi:
In file included from main.c:3:
math_utils.h:3:5: error: redefinition of 'kuadrat'
3 | int kuadrat(int x) {
| ^~~~~~~
In file included from main.c:2:
math_utils.h:3:5: note: previous definition of 'kuadrat' with type 'int(int)'Compiler menolak karena kuadrat didefinisikan dua kali. Untuk macro PI, redefinisi tidak fatal—preprocessor hanya memunculkan peringatan. Tetapi untuk definisi fungsi atau tipe, redefinition adalah error yang menghentikan build.
Pola Include Guard #
Solusi standar adalah membungkus isi header dengan tiga directive: #ifndef TOKEN, #define TOKEN, dan #endif. Pada inclusion pertama, token TOKEN belum didefinisikan, sehingga #ifndef terpenuhi dan preprocessor memproses seluruh isi header—termasuk #define TOKEN. Pada inclusion kedua dan seterusnya, TOKEN sudah didefinisikan, sehingga #ifndef gagal dan seluruh isi header dilewati.
File math_utils.h (dengan guard):
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
#define PI 3.14159265
int kuadrat(int x) {
return x * x;
}
#endif /* MATH_UTILS_H */File util.h:
#ifndef UTIL_H
#define UTIL_H
#include "math_utils.h" /* Util butuh PI dari math_utils */
double luas_lingkaran(double r) {
return PI * r * r;
}
#endif /* UTIL_H */File main.c:
#include <stdio.h>
#include "math_utils.h" /* inclusion pertama */
#include "util.h" /* inclusion kedua (via util.h) */
#include "math_utils.h" /* inclusion ketiga eksplisit */
int main(void) {
printf("kuadrat(5) = %d\n", kuadrat(5));
printf("luas_lingkaran(2.0) = %.4f\n", luas_lingkaran(2.0));
return 0;
}Kompilasi dan jalankan:
gcc -std=c11 -Wall -Wextra -I. -o app main.c && ./appOutput yang Dihasilkan:
kuadrat(5) = 25
luas_lingkaran(2.0) = 12.5664Tiga kali inclusion math_utils.h (satu eksplisit di baris 2, satu tidak langsung via util.h, satu eksplisit lagi di baris 4) tidak menyebabkan error. Inclusion pertama mendefinisikan MATH_UTILS_H dan memproses isi header; inclusion kedua dan ketiga melihat MATH_UTILS_H sudah ada, sehingga #ifndef gagal dan seluruh blok dilewati.
Mari kita telaah baris-baris kunci header di atas:
- Baris 1 (
#ifndef MATH_UTILS_H): pemeriksaan awal. TokenMATH_UTILS_Hdipilih sedemikian rupa sehingga kemungkinan tabrakan dengan nama lain minimal. Konvensi umum:<NAMA_FILE>_H, atau lebih aman lagi<PREFIX>_<NAMA>_Hdengan prefix namespace proyek. - Baris 2 (
#define MATH_UTILS_H): tanda bahwa header sudah diproses. Tidak perlu nilai; keberadaan nama saja sudah cukup. - Baris 4-8: isi header—
#define PIdan definisi fungsikuadrat. Bagian ini hanya diproses sekali per translation unit. - Baris 10 (
#endif /* MATH_UTILS_H */): penutup blok. Komentar/* MATH_UTILS_H */membantu pembaca mengetahui#endifini menutup blok mana—praktik yang sangat berguna saat header memuat banyak#ifbertingkat. - Pada
util.hbaris 4 (#include "math_utils.h"): header lain membutuhkanPI. Inclusion ini akan mengaktifkan#ifndefpada inclusion pertama dariutil.hdimain.c; jikamath_utils.hsudah di-include sebelumnya,#ifndefgagal dan tidak ada kode ganda.