Header yang baik bukan sekadar tempat menaruh deklarasi—header adalah interface publik sebuah modul. Header yang rapi mempercepat kompilasi, mencegah dependensi melingkar, dan mempermudah pemeliharaan jangka panjang. Aturan utamanya: header hanya berisi apa yang pengguna modul perlu tahu, sementara detail implementasi disimpan di file .c. Pada chapter ini kita akan mempraktikkan pemisahan deklarasi (.h) dan definisi (.c), membahas apa yang boleh dan tidak boleh ada di header, dan menyusun satu modul counter lengkap dengan API publik dan struct terbuka.
Aturan Dasar: Header sebagai Interface Publik #
Modul C yang sehat terdiri dari pasangan nama_modul.h + nama_modul.c. File .h berisi tipe publik, deklarasi fungsi, dan macro yang dipakai pengguna. File .c berisi definisi fungsi, variabel statis internal, dan helper yang tidak diekspos. Pemisahan ini memberi dua manfaat: pertama, pengguna modul hanya perlu membaca file .h untuk memahami API; kedua, perubahan implementasi di .c tidak memicu rekompilasi file lain yang hanya meng-include .h.
Hal-hal yang boleh ada di header:
- Deklarasi fungsi (
int jumlah(int a, int b);) - Definisi
structdanunionyang dipakai publik - Definisi
typedefpublik - Deklarasi
externuntuk variabel global publik - Macro konstanta dan macro berparameter yang dipakai publik
- Include guard atau
#pragma once #includeuntuk header lain yang benar-benar dipakai di header ini
Hal-hal yang tidak boleh ada di header:
- Definisi fungsi non-inline (menyebabkan multiple definition saat di-include berkali-kali)
- Definisi variabel (bukan deklarasi
extern) - Kode implementasi rahasia atau helper internal
#includeuntuk header yang hanya dipakai di file.c, bukan di header
Contoh: Modul counter #
Mari kita bangun modul kecil bernama counter yang mengelola sebuah penghitung dengan batas maksimum. Header counter.h mengekspos tipe Counter, fungsi counter_init, counter_tambah, dan counter_get. File counter.c berisi definisi fungsinya.
File counter.h:
#ifndef COUNTER_H
#define COUNTER_H
#include <stddef.h>
/* Tipe publik */
typedef struct {
int nilai;
int batas;
} Counter;
/* API publik */
void counter_init(Counter *c, int batas);
void counter_tambah(Counter *c);
int counter_get(const Counter *c);
#endif /* COUNTER_H */File counter.c:
#include "counter.h"
void counter_init(Counter *c, int batas) {
c->nilai = 0;
c->batas = batas;
}
void counter_tambah(Counter *c) {
if (c->nilai < c->batas) {
c->nilai++;
}
}
int counter_get(const Counter *c) {
return c->nilai;
}File main.c:
#include <stdio.h>
#include "counter.h"
int main(void) {
Counter c;
counter_init(&c, 3);
for (int i = 0; i < 5; i++) {
counter_tambah(&c);
printf("Iterasi %d: counter = %d\n", i, counter_get(&c));
}
return 0;
}Kompilasi dan jalankan:
gcc -std=c11 -Wall -Wextra -I. -o app main.c counter.c && ./appOutput yang Dihasilkan:
Iterasi 0: counter = 1
Iterasi 1: counter = 2
Iterasi 2: counter = 3
Iterasi 3: counter = 3
Iterasi 4: counter = 3Perhatikan bahwa counter tidak melampaui 3 walau counter_tambah dipanggil dua kali lagi. Logika clamping ini ada di counter.c, sementara main.c tidak perlu tahu detailnya—main.c hanya melihat API di counter.h.
Mari kita telaah baris-baris kunci header di atas:
- Baris 1-2 (
#ifndef COUNTER_H/#define COUNTER_H): include guard. Header boleh di-include berkali-kali tanpa redefinition. - Baris 4 (
#include <stddef.h>): header standar yang diperlukan karenacounter.hmemakaisize_tdi sini (sebagai contoh—meski di kode di atassize_tbelum dipakai, pola ini umum untuk modul yang memakai tipe ukuran). Aturan: hanya include apa yang benar-benar dipakai di header. - Baris 7-10 (
typedef struct { ... } Counter;): definisi tipe publik. Pengguna modul dapat membuat variabelCounter c;karena tipeCounterterlihat di header. - Baris 13-15 (deklarasi fungsi): tanda titik koma tanpa badan. Tidak ada implementasi; implementasi ada di
counter.c. Parameterconst Counter *cpadacounter_getmemberi sinyal ke pembaca API bahwa fungsi tidak akan memodifikasi counter. - Pada
counter.cbaris 1 (#include "counter.h"): file.cwajib meng-include header pasangannya agar compiler dapat memverifikasi bahwa definisi fungsi di.ccocok dengan deklarasi di.h. - Pada
counter.cbaris 4-6: definisicounter_init. Kompiler memeriksa tanda tangan fungsi terhadap deklarasi di header. Jika ada ketidakcocokan, compiler melaporkan error.
Header Minimal dan Prinsip Include What You Use #
Praktik penting: header hanya boleh meng-include header lain yang benar-benar dipakai oleh isi header sendiri. Header yang berisi deklarasi fungsi int jumlah(int, int); tidak memerlukan <stdio.h>—meski nantinya implementasi fungsi di file .c mungkin memakai I/O. Memasukkan <stdio.h> di header hanya karena “mungkin nanti dipakai” memperlambat kompilasi seluruh proyek karena setiap file yang meng-include header tersebut juga akan meng-include <stdio.h>.
File math_ops.h (minimal):
#ifndef MATH_OPS_H
#define MATH_OPS_H
/* Header minimal: hanya deklarasi publik, tanpa include yang tidak perlu */
int jumlah(int a, int b);
int kali(int a, int b);
#endifFile math_ops.c:
/* Tidak include <stdio.h> karena math_ops.c tidak membutuhkan I/O */
#include "math_ops.h"
int jumlah(int a, int b) {
return a + b;
}
int kali(int a, int b) {
return a * b;
}File main.c:
#include <stdio.h>
#include "math_ops.h" /* Hanya butuh deklarasi publik */
int main(void) {
printf("5 + 3 = %d\n", jumlah(5, 3));
printf("5 * 3 = %d\n", kali(5, 3));
return 0;
}Kompilasi dan jalankan:
gcc -std=c11 -Wall -Wextra -I. -o app main.c math_ops.c && ./appOutput yang Dihasilkan:
5 + 3 = 8
5 * 3 = 15Perhatikan bahwa math_ops.h tidak meng-include <stdio.h> sama sekali. Hal ini menunjukkan prinsip include what you use: header hanya membawa dependensi yang benar-benar dipakainya. Bila kelak ada 50 file .c lain yang meng-include math_ops.h, mereka tidak akan dipaksa ikut membuka <stdio.h> (yang berisi ratusan deklarasi) tanpa alasan.