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 struct dan union yang dipakai publik
  • Definisi typedef publik
  • Deklarasi extern untuk variabel global publik
  • Macro konstanta dan macro berparameter yang dipakai publik
  • Include guard atau #pragma once
  • #include untuk 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
  • #include untuk 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:

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

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:

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:

bash
gcc -std=c11 -Wall -Wextra -I. -o app main.c counter.c && ./app

Output yang Dihasilkan:

text
Iterasi 0: counter = 1
Iterasi 1: counter = 2
Iterasi 2: counter = 3
Iterasi 3: counter = 3
Iterasi 4: counter = 3

Perhatikan 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:

  1. Baris 1-2 (#ifndef COUNTER_H / #define COUNTER_H): include guard. Header boleh di-include berkali-kali tanpa redefinition.
  2. Baris 4 (#include <stddef.h>): header standar yang diperlukan karena counter.h memakai size_t di sini (sebagai contoh—meski di kode di atas size_t belum dipakai, pola ini umum untuk modul yang memakai tipe ukuran). Aturan: hanya include apa yang benar-benar dipakai di header.
  3. Baris 7-10 (typedef struct { ... } Counter;): definisi tipe publik. Pengguna modul dapat membuat variabel Counter c; karena tipe Counter terlihat di header.
  4. Baris 13-15 (deklarasi fungsi): tanda titik koma tanpa badan. Tidak ada implementasi; implementasi ada di counter.c. Parameter const Counter *c pada counter_get memberi sinyal ke pembaca API bahwa fungsi tidak akan memodifikasi counter.
  5. Pada counter.c baris 1 (#include "counter.h"): file .c wajib meng-include header pasangannya agar compiler dapat memverifikasi bahwa definisi fungsi di .c cocok dengan deklarasi di .h.
  6. Pada counter.c baris 4-6: definisi counter_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):

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

#endif

File math_ops.c:

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:

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:

bash
gcc -std=c11 -Wall -Wextra -I. -o app main.c math_ops.c && ./app

Output yang Dihasilkan:

text
5 + 3 = 8
5 * 3 = 15

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