Saat sebuah program C tumbuh melewati 1.000 baris, menempatkan semua kode di satu file .c menjadi tidak realistis: kompilasi lambat, navigasi kode sulit, dan kolaborasi tim mustahil karena semua developer harus mengedit file yang sama. Pola standar C untuk mengatasi ini adalah memecah program menjadi modul—setiap modul adalah pasangan file .h (header, berisi deklarasi publik) dan .c (sumber, berisi implementasi). Bab ini membahas tiga aturan inti modular design di C: header sebagai interface, file sumber sebagai implementation, dan pemisahan simbol publik vs privat melalui keyword static. Kita juga akan melihat pola opaque type untuk menyembunyikan detail struct agar klien tidak dapat mengakses field internal secara langsung.
Aturan Dasar Pasangan .h/.c #
Setiap modul C mengikuti tiga aturan inti:
- Header (
.h) berisi deklarasi publik: prototype fungsi, deklarasi tipe, makro, dan deklarasi variabel global jika benar-benar diperlukan. - Sumber (
.c) berisi implementasi fungsi-fungsi yang dideklarasikan di header, plus fungsi-fungsi helper yang ditandaistaticagar tidak terlihat di luar modul. - Header harus dapat di-include berkali-kali tanpa error—oleh karena itu setiap header memakai include guard atau
#pragma once.
Konvensi penamaan: nama file mencerminkan nama modul. Modul stack ditulis sebagai stack.h dan stack.c. Simbol publik diberi prefix modul: stack_push, stack_pop. Simbol internal (helper) ditandai static sehingga tetap di file objek dan tidak masuk ke tabel simbol global.
Contoh Modul Lengkap: Stack #
Berikut modul stack lengkap yang menerapkan ketiga aturan di atas. Detail implementasi struct disembunyikan dari header—klien hanya memegang pointer ke stack_t tanpa tahu layout field di dalamnya.
/* File: stack.h - API publik modul stack */
#ifndef STACK_H
#define STACK_H
#include <stddef.h>
/* Tipe publik: forward declaration ke struct yang detailnya
disembunyikan di stack.c (opaque type, lihat bab khusus). */
typedef struct stack stack_t;
/* Konstruktor: alokasi stack baru. NULL jika gagal. */
stack_t *stack_buat(size_t kapasitas_awal);
/* Destruktor: bebaskan memori. Set *s = NULL setelah bebas. */
void stack_hancurkan(stack_t **s);
/* Push value ke puncak stack. 0=OK, -1=error (OOM atau NULL). */
int stack_push(stack_t *s, int value);
/* Pop value dari puncak stack. 0=OK, -1=stack kosong atau NULL. */
int stack_pop(stack_t *s, int *out_value);
/* Jumlah elemen saat ini. */
size_t stack_ukuran(const stack_t *s);
#endif /* STACK_H *//* File: stack.c - Implementasi modul stack (private) */
#include "stack.h"
#include <stdlib.h>
#include <string.h>
/* Detail struct disembunyikan dari header. Hanya stack.c yang tahu
layout lengkap stack_t. Klien hanya memegang pointer. */
struct stack {
int *data;
size_t kapasitas;
size_t ukuran;
};
stack_t *stack_buat(size_t kapasitas_awal)
{
if (kapasitas_awal == 0) {
kapasitas_awal = 8;
}
stack_t *s = malloc(sizeof(*s));
if (s == NULL) return NULL;
s->data = malloc(kapasitas_awal * sizeof(int));
if (s->data == NULL) {
free(s);
return NULL;
}
s->kapasitas = kapasitas_awal;
s->ukuran = 0;
return s;
}
void stack_hancurkan(stack_t **s)
{
if (s == NULL || *s == NULL) return;
free((*s)->data);
free(*s);
*s = NULL;
}
int stack_push(stack_t *s, int value)
{
if (s == NULL) return -1;
if (s->ukuran >= s->kapasitas) {
size_t kap_baru = s->kapasitas * 2;
int *p = realloc(s->data, kap_baru * sizeof(int));
if (p == NULL) return -1;
s->data = p;
s->kapasitas = kap_baru;
}
s->data[s->ukuran++] = value;
return 0;
}
int stack_pop(stack_t *s, int *out_value)
{
if (s == NULL || out_value == NULL) return -1;
if (s->ukuran == 0) return -1;
*out_value = s->data[--s->ukuran];
return 0;
}
size_t stack_ukuran(const stack_t *s)
{
return (s == NULL) ? 0 : s->ukuran;
}/* File: main_stack.c - Klien modul stack */
#include <stdio.h>
#include "stack.h"
int main(void)
{
stack_t *s = stack_buat(2);
if (s == NULL) {
fprintf(stderr, "stack_buat gagal\n");
return 1;
}
/* Push 5 elemen ke stack dengan kapasitas awal 2 - akan realoc */
for (int i = 10; i <= 50; i += 10) {
if (stack_push(s, i) != 0) {
fprintf(stderr, "stack_push gagal pada %d\n", i);
stack_hancurkan(&s);
return 1;
}
printf("Push %d, ukuran sekarang %zu\n", i, stack_ukuran(s));
}
/* Pop semua elemen (LIFO) */
int v;
while (stack_pop(s, &v) == 0) {
printf("Pop %d, ukuran sekarang %zu\n", v, stack_ukuran(s));
}
stack_hancurkan(&s);
printf("stack setelah hancurkan: %p\n", (void *)s);
return 0;
}Compile dengan dua file sumber menjadi satu executable:
gcc -std=c11 -Wall -Wextra -o demo_stack stack.c main_stack.c
./demo_stackOutput yang Dihasilkan:
Push 10, ukuran sekarang 1
Push 20, ukuran sekarang 2
Push 30, ukuran sekarang 3
Push 40, ukuran sekarang 4
Push 50, ukuran sekarang 5
Pop 50, ukuran sekarang 4
Pop 40, ukuran sekarang 3
Pop 30, ukuran sekarang 2
Pop 20, ukuran sekarang 1
Pop 10, ukuran sekarang 0
stack setelah hancurkan: (nil)Mari kita telaah baris-baris kode di atas:
stack.hbaris 4-5 (#ifndef STACK_H / #define STACK_H): Include guard klasik. Jikastack.hdi-include dua kali (langsung maupun transitif melalui header lain), baris-baris antara#ifndefdan#endifhanya diproses sekali. Ini mencegah error “redefinition of struct” yang akan terjadi pada file header besar.stack.hbaris 10 (typedef struct stack stack_t;): Forward declaration. Header mengatakan “ada struct bernamastack, dan namanyastack_t”, tanpa membongkar isi struct. Inilah inti opaque type—klien tidak bisa mengaksess->dataataus->kapasitaskarena layout tidak terlihat di header.stack.hbaris 13-25: Prototype fungsi publik. Setiap fungsi diberi prefixstack_untuk menghindari bentrokan dengan modul lain. Konvensi penamaan parameter (kapasitas_awal,out_value) menjelaskan peran tanpa komentar tambahan.stack.cbaris 9-13 (struct stack { ... };): Definisi lengkap struct hanya ada di file.c. Klien yang meng-includestack.htidak tahu field apa saja di dalamnya. Jika kita mengubah layout struct (mis. menambah fieldpeek_countuntuk statistik), semua klien tidak perlu di-recompile—hanyastack.cyang perlu di-compile ulang.stack.cbaris 16-28 (stack_buat): Konstruktor yang mengalokasikan struct dan buffer data. Pola dua tahap (malloc structlalumalloc data) penting karena struct dan array biasanya punya lifetime berbeda—reallocpada array tidak boleh mengubah pointer struct.stack.cbaris 31-36 (stack_hancurkan): Menerimastack_t **(pointer ke pointer) agar dapat meng-set*s = NULLdi pemanggil. Pola ini mencegah use-after-free: jika klien lupa bahwassudah dibebaskan, NULL dereference akan crash cepat alih-alih merusak silently.main_stack.c: Klien hanya memakai API publik. Tidak ada#include <stdlib.h>atau#include <string.h>di sini karena klien tidak perlu tahu bahwa implementasistack_tmemakaimalloc.
Public vs Private API #
Pemisahan publik vs privat di C dicapai melalui tiga mekanisme:
| Mekanisme | Visibilitas | Contoh |
|---|---|---|
| Deklarasi di header | Publik (dapat di-include siapa saja) | stack_t, stack_push |
Helper dengan static | Privat (hanya di file .c itu) | static int grow(stack_t*) |
Macro tanpa static | Bisa di-include tetapi tidak punya alamat | #define ARR_LEN(arr) ... |
Berikut demonstrasi tambahan yang menunjukkan bagaimana satu file tunggal dapat memakai pola header inline sederhana:
/* File: test_modul2.c - Pola include guard dan header library */
#include <stdio.h>
/* Contoh header inline yang menunjukkan include guard dan deklarasi */
#define VERSION_H
#ifdef VERSION_H
static const char *APP_VERSION = "1.2.3";
static const char *APP_NAME = "demo-modul";
#endif
/* Fungsi pembungkus untuk menampilkan info modul */
static void tampilkan_info(void)
{
printf("%s versi %s\n", APP_NAME, APP_VERSION);
printf("Kompilasi: %s %s\n", __DATE__, __TIME__);
printf("Standard C: %ld\n", __STDC_VERSION__);
}
int main(void)
{
tampilkan_info();
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -o test_modul2 test_modul2.c
./test_modul2Output yang Dihasilkan:
demo-modul versi 1.2.3
Kompilasi: Oct 13 2026 09:23:34
Standard C: 201112Nilai __DATE__, __TIME__, dan __STDC_VERSION__ adalah predefined macro yang di-substitusi preprocessor. __STDC_VERSION__ bernilai 201112L untuk C11, 201710L untuk C17, dan 202311L untuk C23. Macro ini berguna untuk logging versi build dan kompabilitas standar.
Aturan Tambahan untuk Modul yang Sehat #
Beberapa aturan praktis yang kami ikuti di proyek C:
- Satu modul = satu file
.c. Jangan menggabungkan dua modul (stackdanqueue) ke filecontainers.c. Pemisahan fisik membantu navigasi dan build incremental. - Header tidak boleh berisi definisi fungsi kecuali
static inlineyang sengaja untuk optimasi. Definisi fungsi biasa di header menyebabkan multiple definition error saat linking dua file yang meng-include header tersebut. - Header tidak boleh
#includeheader lain yang tidak strictly diperlukan. Mis.stack.hhanya butuh<stddef.h>untuksize_t; jangan#include <stdio.h>hanya karenastack.cmemakainya. Hal ini mengurangi waktu kompilasi. - Header memakai include guard (
#ifndef MODUL_H…#define MODUL_H…#endif) atau#pragma once. Standar C23 menambahkan#pragma oncesebagai standar, tetapi compiler mayoritas sudah mendukungnya sejak lama. - File
.cmeng-include header sendiri pertama kali. Pola ini menjamin bahwa header bersifat self-contained—tidak menyembunyikan dependency ke file lain. Jikastack.hternyata butuh#include <stdlib.h>tetapi lupa mendeklarasikannya, error akan muncul saatstack.c(yang meng-includestack.hduluan) dikompilasi.