Pemisahan file .c (source) dan .h (header) adalah konvensi inti dalam proyek C berskala menengah ke atas. File .c berisi definisi—implementasi fungsi, variabel global, dan kode program. File .h berisi deklarasi—kontrak publik yang dilihat pengguna modul. Memahami peran kedua jenis file ini penting karena pola yang sama dipakai oleh hampir seluruh pustaka C, mulai dari standar pustaka sampai framework besar seperti OpenSSL atau SQLite. Pada chapter ini kita akan membedakan tugas kedua jenis file, melihat peran static helper yang disembunyikan di file .c, dan menyusun satu modul teks lengkap dengan API publik dan helper internal.
Peran File .c dan .h #
File .c adalah tempat kode program berada. Setiap fungsi yang dipakai pengguna modul maupun fungsi helper internal didefinisikan di sini. File .c boleh berisi variabel global yang dapat diakses lintas file (dengan bantuan extern di header), tetapi harus didefinisikan di satu tempat saja. Setiap file .c adalah satu translation unit terpisah yang dikompilasi menjadi satu object file (.o).
File .h adalah interface publik. File ini berisi deklarasi fungsi publik, definisi tipe publik, dan macro publik. File .h tidak boleh berisi definisi fungsi non-inline atau definisi variabel karena akan menyebabkan multiple definition saat di-include oleh banyak file .c. File .h juga tempat mekanisme include guard atau `#pragma once* berada.
Mekanisme kerja kedua jenis file terhubung saat compiler memproses file .c: preprocessor menyisipkan isi header yang di-include ke dalam translation unit file .c, lalu compiler memprosesnya menjadi object file. Linker kemudian menyatukan object file dari berbagai file .c menjadi satu executable. Karena header tidak dikompilasi terpisah, perubahan di header memaksa rekompilasi semua file .c yang meng-include header tersebut.
Demonstrasi: Modul teks #
File teks.h berisi deklarasi tiga fungsi publik. File teks.c berisi definisi fungsi-fungsi tersebut plus sebuah helper internal cetak_dengan_prefix yang tidak pernah diekspos ke pengguna modul.
File teks.h:
#ifndef TEKS_H
#define TEKS_H
/* Deklarasi publik -- kontrak interface */
void cetak_tebal(const char *s);
void cetak_miring(const char *s);
int panjang(const char *s);
#endifFile teks.c:
#include <stdio.h>
#include "teks.h"
/* Helper internal (static, tidak keluar file) */
static void cetak_dengan_prefix(const char *prefix, const char *s) {
printf("[%s] %s\n", prefix, s);
}
void cetak_tebal(const char *s) {
cetak_dengan_prefix("BOLD", s);
}
void cetak_miring(const char *s) {
cetak_dengan_prefix("ITALIC", s);
}
int panjang(const char *s) {
int n = 0;
while (s[n] != '\0') n++;
return n;
}File main.c:
#include <stdio.h>
#include "teks.h"
int main(void) {
const char *pesan = "Halo Dunia";
cetak_tebal(pesan);
cetak_miring(pesan);
printf("panjang(\"%s\") = %d\n", pesan, panjang(pesan));
return 0;
}Kompilasi dan jalankan:
gcc -std=c11 -Wall -Wextra -I. -o app main.c teks.c && ./appOutput yang Dihasilkan:
[BOLD] Halo Dunia
[ITALIC] Halo Dunia
panjang("Halo Dunia") = 10Perhatikan bahwa main.c hanya meng-include teks.h, bukan <stdio.h>—kita sengaja meng-include <stdio.h> di main.c karena main.c memakai printf langsung. Implementasi cetak_dengan_prefix di teks.c juga memakai printf, tetapi <stdio.h> hanya di-include di teks.c, bukan di header. Pemisahan ini menjaga teks.h tetap minimal.
Mari kita telaah baris-baris kunci di atas:
- Baris 1-3 pada
teks.c(#include <stdio.h>dan#include "teks.h"): urutan import biasanya header sistem dulu, lalu header lokal.teks.cmembutuhkan<stdio.h>untukprintfdan"teks.h"agar tanda tangan fungsi yang didefinisikan cocok dengan deklarasi publik di header. - Baris 6-8 pada
teks.c(static void cetak_dengan_prefix(...)): fungsi helper internal. Keywordstaticdi sini memberi fungsi tersebut internal linkage—artinya tidak terlihat dari file.clain saat linking. Fungsicetak_dengan_prefixtidak dideklarasikan diteks.h, sehingga pengguna modul tidak dapat memanggilnya. - Baris 10-12 (
void cetak_tebal(const char *s)): definisi fungsi publik. Tanda tangan harus persis cocok dengan deklarasi di header. Jika tidak cocok, compiler melaporkan error konflik tipe. - Baris 16-20 (
int panjang(...)): implementasi sederhana menghitung panjang string. Fungsi ini tidak membutuhkan<stdio.h>secara langsung—tidak adaprintfdi dalamnya. Jika kita memisahpanjangke fileteks_util.cyang terpisah, kita bisa saja memilih untuk tidak meng-include<stdio.h>di file tersebut. - Pada
main.cbaris 2 (#include "teks.h"):main.chanya melihat tiga fungsi publik. Ia tidak tahu adanyacetak_dengan_prefix. Pemisahan ini memberi encapsulation ala OOP: detail implementasi tersembunyi.
Membuktikan static Menyembunyikan Helper #
Untuk memperjelas efek static, mari kita coba memanggil cetak_dengan_prefix dari file lain. Karena fungsi tidak dideklarasikan di header, compiler akan melaporkan implicit declaration error:
#include <stdio.h>
#include "teks.h"
int main(void) {
/* Mencoba memanggil helper internal -- seharusnya error */
cetak_dengan_prefix("X", "Y");
return 0;
}Output yang Dihasilkan saat kompilasi:
bad_main.c:6:5: error: implicit declaration of function 'cetak_dengan_prefix'
6 | cetak_dengan_prefix("X", "Y");
| ^~~~~~~~~~~~~~~~~~~Compiler menolak karena cetak_dengan_prefix tidak dideklarasikan di header mana pun. Inilah cara C mencapai information hiding: fungsi atau variabel static hanya terlihat dalam translation unit tempat ia didefinisikan. Bahkan jika kita menambahkan deklarasi cetak_dengan_prefix secara manual di bad_main.c, linker akan melaporkan undefined reference karena internal linkage menghalangi simbol dilihat dari object file lain.
Praktik Penamaan dan Struktur Direktori #
Untuk proyek dengan beberapa modul, konvensi umum adalah menempatkan semua file .c dan .h di direktori terpisah. Misalnya:
myapp/
├── include/ # header publik (diakses oleh pengguna modul)
│ ├── teks.h
│ └── math_ops.h
├── src/ # source file (implementasi)
│ ├── teks.c
│ └── math_ops.c
└── main.c # entry pointSaat kompilasi, flag -Iinclude memberitahu GCC untuk mencari header di folder include/. Struktur seperti ini umum dijumpai di proyek open-source dan memudahkan distribusi: pengguna hanya perlu mengakses include/ dan berkas object file (atau static library .a), tanpa harus membaca src/.