Membangun modul C berarti merancang satu unit .h + .c yang dapat dipakai ulang di banyak program. Modul yang baik memiliki interface publik yang jelas, implementation tersembunyi, manajemen sumber daya yang aman, dan kode yang dapat diuji secara terpisah. Setelah mempelajari include guard, separate compilation, linkage, dan extern pada chapter-chapter sebelumnya, sekarang saatnya menyatukan semuanya menjadi satu modul lengkap. Pada chapter ini kita akan merancang modul Stack dengan opaque pointer—pola idiomatik C untuk encapsulation penuh—lalu membangun modul tersebut menjadi executable.
Pola Opaque Pointer untuk Encapsulation #
Pola opaque pointer menyembunyikan detail struct dari header. Header hanya menampilkan typedef struct Stack Stack;—deklarasi forward tanpa definisi isi. Pengguna modul hanya memegang pointer ke Stack dan tidak dapat mengakses field internal karena compiler tidak tahu ukuran atau isi struct Stack. Definisi lengkap struct Stack { ... }; hanya muncul di file .c.
Keuntungan pola ini:
- Encapsulation penuh: pengguna tidak dapat mengakses field internal
data,kapasitas, atautopdari luar filestack.c. - Stabilitas ABI: jika kita mengubah isi
struct Stack(mis. menambah field baru), pengguna modul tidak perlu dikompilasi ulang karena ukuranstruct Stack*tetap sama (pointer). - Fleksibilitas implementasi: kita dapat mengganti implementasi array dinamis dengan linked list tanpa mengubah header.
Mekanisme kerja di balik layar: pengguna modul memegang pointer ke Stack—pointer bertipe Stack*. Karena tipe Stack tidak lengkap di header, pengguna tidak dapat membuat variabel Stack s; secara langsung (compiler tidak tahu ukurannya). Pengguna wajib memanggil stack_create() untuk mendapatkan pointer ke objek yang dialokasikan di file stack.c, di mana definisi lengkap struct Stack terlihat.
Implementasi Modul Stack #
File stack.h:
#ifndef STACK_H
#define STACK_H
#include <stddef.h>
#include <stdbool.h>
/* Opaque handle -- detail implementasi tersembunyi */
typedef struct Stack Stack;
/* API publik */
Stack *stack_create(size_t kapasitas);
void stack_destroy(Stack *s);
bool stack_push(Stack *s, int nilai);
bool stack_pop(Stack *s, int *out);
size_t stack_size(const Stack *s);
#endif /* STACK_H */File stack.c:
#include "stack.h"
#include <stdlib.h>
/* Definisi tipe lengkap -- hanya terlihat di file ini */
struct Stack {
int *data;
size_t kapasitas;
size_t top;
};
Stack *stack_create(size_t kapasitas) {
Stack *s = malloc(sizeof(Stack));
if (!s) return NULL;
s->data = malloc(kapasitas * sizeof(int));
if (!s->data) {
free(s);
return NULL;
}
s->kapasitas = kapasitas;
s->top = 0;
return s;
}
void stack_destroy(Stack *s) {
if (!s) return;
free(s->data);
free(s);
}
bool stack_push(Stack *s, int nilai) {
if (!s || s->top >= s->kapasitas) return false;
s->data[s->top++] = nilai;
return true;
}
bool stack_pop(Stack *s, int *out) {
if (!s || s->top == 0) return false;
*out = s->data[--s->top];
return true;
}
size_t stack_size(const Stack *s) {
return s ? s->top : 0;
}File main.c:
#include <stdio.h>
#include "stack.h"
int main(void) {
Stack *s = stack_create(5);
if (!s) {
fprintf(stderr, "Gagal membuat stack\n");
return 1;
}
for (int i = 10; i <= 50; i += 10) {
if (stack_push(s, i)) {
printf("Push: %d (size=%zu)\n", i, stack_size(s));
}
}
int nilai;
while (stack_pop(s, &nilai)) {
printf("Pop: %d (size=%zu)\n", nilai, stack_size(s));
}
stack_destroy(s);
return 0;
}Kompilasi dan jalankan:
gcc -std=c11 -Wall -Wextra -I. -o app main.c stack.c && ./appOutput yang Dihasilkan:
Push: 10 (size=1)
Push: 20 (size=2)
Push: 30 (size=3)
Push: 40 (size=4)
Push: 50 (size=5)
Pop: 50 (size=4)
Pop: 40 (size=3)
Pop: 30 (size=2)
Pop: 20 (size=1)
Pop: 10 (size=0)Perhatikan bahwa Stack berperilaku seperti tipe data abstrak: pengguna hanya berinteraksi melalui lima fungsi publik. Urutan pop bersifat LIFO (Last-In-First-Out)—elemen terakhir yang dimasukkan (50) adalah yang pertama keluar—sesuai dengan semantik struktur data stack.
Mari kita telaah baris-baris kunci di atas:
- Pada
stack.hbaris 8 (typedef struct Stack Stack;): opaque pointer pattern. Kita mendeklarasikan tipeStacksebagai alias kestruct Stackyang tidak lengkap. Pengguna header hanya melihat tipe pointerStack*—tidak bisa membuat variabelStack s;karena ukuranstruct Stacktidak diketahui. - Pada
stack.hbaris 11-15 (API publik): lima fungsi yang membentuk kontrak modul.stack_createadalah factory yang mengalokasikan objek.stack_destroyadalah destructor yang membebaskan memori.stack_pushdanstack_popmemodifikasi state.stack_sizeadalah query read-only (ditandaiconst Stack *). - Pada
stack.cbaris 6-10 (struct Stack { ... };): definisi lengkap tipe. Fielddataadalah array dinamis,kapasitasadalah batas maksimum,topadalah indeks slot kosong berikutnya. Definisi ini hanya terlihat di filestack.c. - Pada
stack.cbaris 12-22 (stack_create): factory function. Alokasi dua tahap—pertamamalloc(sizeof(Stack))untuk struct header, lalumalloc(kapasitas * sizeof(int))untuk array data. Jika alokasi kedua gagal, kita bebaskan alokasi pertama untuk menghindari memory leak. Pola ini disebut two-phase allocation dan merupakan praktik wajib. - Pada
stack.cbaris 24-28 (stack_destroy): destructor. Membebaskan array data dulu, lalu struct. Pemeriksaanif (!s) return;melindungi darifree(NULL)yang sebenarnya aman di C, tetapi pola defensif ini umum dipakai untuk kejelasan. - Pada
stack.cbaris 30-34 (stack_push): penambahan elemen. Pemeriksaans->top >= s->kapasitasmencegah buffer overflow. Jika berhasil, nilai disalin kes->data[s->top]lalus->topdinaikkan. - Pada
stack.cbaris 36-40 (stack_pop): pengambilan elemen. Pre-decrement--s->topmemindahkan indeks ke elemen paling atas. Out-parameter*outmenerima nilai yang diambil, sehingga pemanggil dapat memeriksa keberhasilan via return valueboolsekaligus menerima data via pointer. - Pada
main.cbaris 7 (Stack *s = stack_create(5);): pengguna memegang pointer ke objek yang dialokasikan modul. Pengguna tidak tahu (dan tidak perlu tahu) bagaimanastruct Stackterstruktur. - Pada
main.cbaris 22 (stack_destroy(s);): pemanggilan destructor setelah selesai. Pola create-use-destroy adalah ciri khas API C berbasis opaque pointer.
Membangun Modul sebagai Static Library #
Setelah modul berfungsi, kita dapat membangunnya menjadi static library untuk dipakai ulang di banyak program tanpa kompilasi ulang. Alur kerjanya:
# Kompilasi stack.c menjadi object file
gcc -std=c11 -Wall -Wextra -c stack.c -o stack.o
# Arsipkan ke static library libstack.a
ar rcs libstack.a stack.o
# Program lain dapat meng-link dengan libstack.a
gcc -std=c11 -Wall -Wextra main.c -L. -lstack -o appDistribusi modul cukup memberikan dua file: stack.h (header publik) dan libstack.a (object file terkompilasi). Pengguna tidak perlu melihat stack.c; modul benar-benar encapsulated.
Praktik yang Membuat Modul Bertahan Lama #
Beberapa praktik yang membedakan modul C yang baik dari modul sekadar jalan:
- API minimal: hanya ekspos fungsi yang benar-benar dipakai pengguna. Helper internal wajib
static. - Dokumentasi kontrak: setiap fungsi publik wajib menjelaskan precondition, postcondition, dan ownership memori (siapa yang harus membebaskan hasil
malloc). - Error handling konsisten: gunakan
bool/intreturn code untuk fungsi yang dapat gagal, danNULLuntuk factory yang gagal alokasi. - Const-correctness: parameter
const Stack *padastack_sizememberi sinyal fungsi tidak memodifikasi state. - Tes terpisah: tulis file
test_stack.cyang menguji API secara menyeluruh sebelum modul dirilis.