Empat struktur data yang kita bangun pada bagian 1-4 mengalokasikan node secara dinamis di heap dengan malloc. Tanpa kebalikan free, node yang tidak terpakai tetap di heap dan terakumulasi — memory leak. Pada bagian ini kita memperbaiki keempat modul dengan menambahkan fungsi _destroy konsisten dan mendemonstrasikan realloc untuk resize buffer dinamis. Tujuan bagian ini adalah menetapkan pola manajemen memori yang aman: setiap _init harus dipasangkan dengan _destroy.
Konsep #
Aturan dasar manajemen memori C: setiap malloc harus dipasangkan dengan free pada pointer yang sama. Bila kita malloc tiga node, kita harus free ketiganya. Pola yang kita terapkan: setiap struktur data punya pasangan fungsi _init (set pointer ke NULL, size ke 0) dan _destroy (free semua node, kembalikan ke kondisi kosong).
Mekanisme _destroy untuk struktur berbasis linked list (linked list, stack, queue): traverse node satu per satu, simpan pointer next sebelum free node saat ini. Untuk hash table: iterasi setiap bucket, di setiap bucket traverse linked list dengan pola yang sama. Penting: setelah _destroy, struktur harus dalam kondisi konsisten — semua pointer NULL, size 0 — agar pemanggil tidak tergoda memakai struktur yang sudah di-destroy.
realloc adalah operasi resize: beri pointer ke buffer lama dan ukuran baru, realloc mengalokasikan blok baru, menyalin isi lama, lalu membebaskan blok lama. Pointer yang dikembalikan bisa berbeda dari pointer input — selalu perbarui pointer dengan return value realloc. Bila realloc gagal (return NULL), buffer lama tetap valid — kita bisa mencoba lagi atau mengabaikan resize.
Implementasi #
Fungsi buffer_init, buffer_destroy, dan buffer_resize untuk demo pola manajemen memori:
typedef struct {
int *data;
int size;
} Buffer;
int buffer_init(Buffer *b, int size) {
b->data = (int *)malloc(sizeof(int) * (size_t)size);
if (b->data == 0) {
b->size = 0;
return -1;
}
b->size = size;
for (int i = 0; i < size; i++) b->data[i] = i * 10;
return 0;
}
void buffer_destroy(Buffer *b) {
if (b->data != 0) {
free(b->data);
b->data = 0;
}
b->size = 0;
}
void buffer_resize(Buffer *b, int new_size) {
int *new_data = (int *)realloc(b->data, sizeof(int) * (size_t)new_size);
if (new_data == 0) return; /* b->data tetap valid */
b->data = new_data;
for (int i = b->size; i < new_size; i++) b->data[i] = i * 10;
b->size = new_size;
}Demo mandiri:
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int *data;
int size;
} Buffer;
static int buffer_init(Buffer *b, int size) {
b->data = (int *)malloc(sizeof(int) * (size_t)size);
if (b->data == 0) {
b->size = 0;
return -1;
}
b->size = size;
for (int i = 0; i < size; i++) b->data[i] = i * 10;
return 0;
}
static void buffer_destroy(Buffer *b) {
if (b->data != 0) {
free(b->data);
b->data = 0;
}
b->size = 0;
}
static void buffer_resize(Buffer *b, int new_size) {
int *new_data = (int *)realloc(b->data, sizeof(int) * (size_t)new_size);
if (new_data == 0) return;
b->data = new_data;
for (int i = b->size; i < new_size; i++) b->data[i] = i * 10;
b->size = new_size;
}
int main(void) {
Buffer b;
if (buffer_init(&b, 3) != 0) {
fprintf(stderr, "Alokasi gagal\n");
return 1;
}
printf("Awal size=%d: ", b.size);
for (int i = 0; i < b.size; i++) printf("%d ", b.data[i]);
printf("\n");
buffer_resize(&b, 5);
printf("Setelah resize size=%d: ", b.size);
for (int i = 0; i < b.size; i++) printf("%d ", b.data[i]);
printf("\n");
buffer_destroy(&b);
printf("Setelah destroy size=%d, data=%p\n", b.size, (void *)b.data);
long total = 0;
for (int i = 1; i <= 1000; i++) {
int *p = (int *)malloc(sizeof(int));
if (p != 0) {
*p = i;
total += *p;
free(p);
}
}
printf("Total 1000 alokasi: %ld\n", total);
return 0;
}Output yang Dihasilkan:
Awal size=3: 0 10 20
Setelah resize size=5: 0 10 20 30 40
Setelah destroy size=0, data=(nil)
Total 1000 alokasi: 500500Pengujian #
Build dan jalankan:
gcc -std=c11 -Wall -Wextra -o memory_demo main.c
./memory_demoBuild dengan AddressSanitizer untuk memverifikasi tidak ada leak:
gcc -std=c11 -Wall -Wextra -fsanitize=address -o memory_asan main.c
./memory_asan 2>&1 | tail -3Output yang Dihasilkan (potongan akhir):
Total 1000 alokasi: 500500Tidak ada pesan leak dari ASan karena setiap malloc (3 + 1000) dipasangkan dengan free. Bila kita menghapus baris free(p) di loop 1000 alokasi, ASan akan melaporkan 1000 leak sebesar 4 byte masing-masing.
Untuk hash table, tambahkan fungsi ht_destroy:
void ht_destroy(HashTable *ht) {
for (int i = 0; i < HT_SIZE; i++) {
HashNode *cur = ht->buckets[i];
while (cur != 0) {
HashNode *next = cur->next;
free(cur);
cur = next;
}
ht->buckets[i] = 0;
}
}Panggil ht_destroy(&ht) di akhir main demo hash table, lalu build dengan ASan — leak sebelumnya (144 byte) hilang.
Mari kita telaah baris-baris kunci pada pola manajemen memori:
- Baris
b->data = (int *)malloc(sizeof(int) * (size_t)size);: alokasi array dinamis.sizeof(int) * sizememberi total byte (4 * size pada sistem 32-bit, 4 * size pada 64-bit —inttetap 4 byte). Cast(size_t)menghindari warning signed-unsigned. - Baris
if (b->data == 0) { b->size = 0; return -1; }: penjaga kegagalan alokasi. Bilamallocgagal (return NULL), kita kembalikan kode error ke pemanggil. Penting: setb->size = 0agar struktur dalam kondisi konsisten meski gagal. - Baris
for (int i = 0; i < size; i++) b->data[i] = i * 10;: inisialisasi data setelah alokasi. Tanpa inisialisasi, array berisi sampah — berguna untuk demo pola inisialisasi. - Baris
if (b->data != 0) { free(b->data); b->data = 0; }(destroy): penjaga NULL. Bilabuffer_destroydipanggil dua kali (double-free), penjaga ini mencegah crash karenafree(NULL)legal (no-op). - Baris
b->size = 0;(destroy): reset size agar struktur dalam kondisi valid setelah destroy. Tanpa baris ini,b->sizeberisi angka lama — pemanggil mungkin tergoda memakaib->data[0]yang sudah di-free. - Baris
int *new_data = (int *)realloc(b->data, sizeof(int) * new_size);: resize denganrealloc. Beri pointer lama dan ukuran baru;reallocalokasi blok baru, salin data lama, lalufreeblok lama. - Baris
if (new_data == 0) return;(resize): kegagalanrealloc. Bilareallocgagal, ia mengembalikan NULL tetapi blok lama TETAP VALID. Kita return tanpa modifikasi — buffer lama tetap bisa dipakai. - Baris
b->data = new_data;(resize): update pointer ke blok baru. Tanpa baris ini,b->datatetap menunjuk ke blok lama yang sudah di-freeolehrealloc— use-after-free. - Baris
for (int i = b->size; i < new_size; i++) b->data[i] = i * 10;: inisialisasi elemen baru.reallochanya menyalin data lama — elemen di[size, new_size)berisi sampah yang perlu diinisialisasi. - Baris
HashNode *next = cur->next; free(cur); cur = next;(ht_destroy): pola yang sama dengan linked list destroy — simpan pointernextSEBELUMfree.
Pada Bagian 6 kita akan menulis rangkaian test yang memverifikasi keempat struktur data bekerja sesuai kontrak, termasuk edge case boundary.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
hash_table.c
Unduh berkas/* hash_table.c -- simple hash table with chaining. */
#define _POSIX_C_SOURCE 200809L /* for strdup */
#include "hash_table.h"
#include <stdlib.h>
#include <string.h>
/* djb2 hash function by Daniel J. Bernstein. */
static unsigned long hash_djb2(const char *str) {
unsigned long h = 5381;
int c;
while ((c = (unsigned char)*str++)) {
h = ((h << 5) + h) + c; /* h * 33 + c */
}
return h;
}
int ht_init(HashTable *ht, size_t capacity) {
if (!ht || capacity == 0) return -1;
ht->buckets = calloc(capacity, sizeof(HTNode *));
if (!ht->buckets) return -1;
ht->capacity = capacity;
ht->size = 0;
return 0;
}
void ht_destroy(HashTable *ht) {
if (!ht) return;
for (size_t i = 0; i < ht->capacity; i++) {
HTNode *cur = ht->buckets[i];
while (cur) {
HTNode *next = cur->next;
free(cur->key);
free(cur);
cur = next;
}
}
free(ht->buckets);
ht->buckets = NULL;
ht->capacity = 0;
ht->size = 0;
}
int ht_put(HashTable *ht, const char *key, int value) {
if (!ht || !key) return -1;
unsigned long h = hash_djb2(key) % ht->capacity;
/* Update if key exists. */
HTNode *cur = ht->buckets[h];
while (cur) {
if (strcmp(cur->key, key) == 0) {
cur->value = value;
return 0;
}
cur = cur->next;
}
/* Insert new node at head. */
HTNode *node = malloc(sizeof(*node));
if (!node) return -1;
node->key = strdup(key);
if (!node->key) {
free(node);
return -1;
}
node->value = value;
node->next = ht->buckets[h];
ht->buckets[h] = node;
ht->size++;
return 0;
}
int ht_get(const HashTable *ht, const char *key, int *out) {
if (!ht || !key) return -1;
unsigned long h = hash_djb2(key) % ht->capacity;
HTNode *cur = ht->buckets[h];
while (cur) {
if (strcmp(cur->key, key) == 0) {
if (out) *out = cur->value;
return 0;
}
cur = cur->next;
}
return -1;
}
int ht_remove(HashTable *ht, const char *key) {
if (!ht || !key) return -1;
unsigned long h = hash_djb2(key) % ht->capacity;
HTNode *cur = ht->buckets[h];
HTNode *prev = NULL;
while (cur) {
if (strcmp(cur->key, key) == 0) {
if (prev) prev->next = cur->next;
else ht->buckets[h] = cur->next;
free(cur->key);
free(cur);
ht->size--;
return 0;
}
prev = cur;
cur = cur->next;
}
return -1;
}
size_t ht_size(const HashTable *ht) {
return ht ? ht->size : 0;
}
linked_list.c
Unduh berkas/* linked_list.c -- singly linked list implementation. */
#include "linked_list.h"
#include <stdio.h>
#include <stdlib.h>
void ll_init(LinkedList *list) {
if (!list) return;
list->head = NULL;
list->size = 0;
}
void ll_destroy(LinkedList *list) {
if (!list) return;
LLNode *cur = list->head;
while (cur) {
LLNode *next = cur->next;
free(cur);
cur = next;
}
list->head = NULL;
list->size = 0;
}
int ll_push_front(LinkedList *list, int value) {
if (!list) return -1;
LLNode *node = malloc(sizeof(*node));
if (!node) return -1;
node->data = value;
node->next = list->head;
list->head = node;
list->size++;
return 0;
}
int ll_push_back(LinkedList *list, int value) {
if (!list) return -1;
LLNode *node = malloc(sizeof(*node));
if (!node) return -1;
node->data = value;
node->next = NULL;
if (!list->head) {
list->head = node;
} else {
LLNode *cur = list->head;
while (cur->next) cur = cur->next;
cur->next = node;
}
list->size++;
return 0;
}
int ll_pop_front(LinkedList *list, int *out) {
if (!list || !list->head) return -1;
LLNode *node = list->head;
if (out) *out = node->data;
list->head = node->next;
free(node);
list->size--;
return 0;
}
int ll_get(const LinkedList *list, size_t index, int *out) {
if (!list || !out || index >= list->size) return -1;
LLNode *cur = list->head;
for (size_t i = 0; i < index; i++) cur = cur->next;
*out = cur->data;
return 0;
}
int ll_remove(LinkedList *list, size_t index) {
if (!list || index >= list->size) return -1;
LLNode *to_free;
if (index == 0) {
to_free = list->head;
list->head = to_free->next;
} else {
LLNode *prev = list->head;
for (size_t i = 0; i < index - 1; i++) prev = prev->next;
to_free = prev->next;
prev->next = to_free->next;
}
free(to_free);
list->size--;
return 0;
}
int ll_find(const LinkedList *list, int value) {
if (!list) return -1;
LLNode *cur = list->head;
int index = 0;
while (cur) {
if (cur->data == value) return index;
cur = cur->next;
index++;
}
return -1;
}
size_t ll_size(const LinkedList *list) {
return list ? list->size : 0;
}
void ll_print(const LinkedList *list) {
if (!list) return;
printf("[");
LLNode *cur = list->head;
while (cur) {
printf("%d", cur->data);
if (cur->next) printf(", ");
cur = cur->next;
}
printf("]\n");
}
main.c
Unduh berkas/* main.c -- demo of all four data structures. */
#include "linked_list.h"
#include "stack.h"
#include "queue.h"
#include "hash_table.h"
#include <stdio.h>
#include <string.h>
static void demo_linked_list(void) {
printf("--- Linked List ---\n");
LinkedList list;
ll_init(&list);
ll_push_back(&list, 10);
ll_push_back(&list, 20);
ll_push_back(&list, 30);
ll_push_front(&list, 5);
printf("After pushes: ");
ll_print(&list);
printf("Size: %zu\n", ll_size(&list));
printf("Find 20: index %d\n", ll_find(&list, 20));
int v;
ll_pop_front(&list, &v);
printf("After pop_front (%d): ", v);
ll_print(&list);
ll_destroy(&list);
printf("\n");
}
static void demo_stack(void) {
printf("--- Stack ---\n");
Stack s;
if (stack_init(&s, 4) != 0) {
printf("stack_init failed\n");
return;
}
stack_push(&s, 1);
stack_push(&s, 2);
stack_push(&s, 3);
printf("Size: %zu\n", stack_size(&s));
int v;
while (!stack_is_empty(&s)) {
stack_pop(&s, &v);
printf("Popped: %d\n", v);
}
stack_destroy(&s);
printf("\n");
}
static void demo_queue(void) {
printf("--- Queue ---\n");
Queue q;
if (queue_init(&q, 4) != 0) {
printf("queue_init failed\n");
return;
}
queue_enqueue(&q, 10);
queue_enqueue(&q, 20);
queue_enqueue(&q, 30);
printf("Size: %zu\n", queue_size(&q));
int v;
while (!queue_is_empty(&q)) {
queue_dequeue(&q, &v);
printf("Dequeued: %d\n", v);
}
queue_destroy(&q);
printf("\n");
}
static void demo_hash_table(void) {
printf("--- Hash Table ---\n");
HashTable ht;
if (ht_init(&ht, 8) != 0) {
printf("ht_init failed\n");
return;
}
ht_put(&ht, "satu", 1);
ht_put(&ht, "dua", 2);
ht_put(&ht, "tiga", 3);
printf("Size: %zu\n", ht_size(&ht));
int v;
if (ht_get(&ht, "dua", &v) == 0) printf("get(dua) = %d\n", v);
if (ht_get(&ht, "empat", &v) != 0) printf("get(empat) not found\n");
ht_remove(&ht, "dua");
printf("After remove dua, size: %zu\n", ht_size(&ht));
ht_destroy(&ht);
printf("\n");
}
int main(void) {
printf("== Struktur Data Demo ==\n\n");
demo_linked_list();
demo_stack();
demo_queue();
demo_hash_table();
printf("== Selesai ==\n");
return 0;
}
queue.c
Unduh berkas/* queue.c -- FIFO queue (circular buffer). */
#include "queue.h"
#include <stdlib.h>
int queue_init(Queue *q, size_t capacity) {
if (!q || capacity == 0) return -1;
q->data = malloc(capacity * sizeof(int));
if (!q->data) return -1;
q->capacity = capacity;
q->head = 0;
q->tail = 0;
q->size = 0;
return 0;
}
void queue_destroy(Queue *q) {
if (!q) return;
free(q->data);
q->data = NULL;
q->capacity = 0;
q->head = q->tail = q->size = 0;
}
int queue_enqueue(Queue *q, int value) {
if (!q || q->size >= q->capacity) return -1;
q->data[q->tail] = value;
q->tail = (q->tail + 1) % q->capacity;
q->size++;
return 0;
}
int queue_dequeue(Queue *q, int *out) {
if (!q || q->size == 0) return -1;
if (out) *out = q->data[q->head];
q->head = (q->head + 1) % q->capacity;
q->size--;
return 0;
}
int queue_is_empty(const Queue *q) {
return !q || q->size == 0;
}
size_t queue_size(const Queue *q) {
return q ? q->size : 0;
}
stack.c
Unduh berkas/* stack.c -- LIFO stack (dynamic array). */
#include "stack.h"
#include <stdlib.h>
int stack_init(Stack *s, size_t capacity) {
if (!s || capacity == 0) return -1;
s->data = malloc(capacity * sizeof(int));
if (!s->data) return -1;
s->capacity = capacity;
s->size = 0;
return 0;
}
void stack_destroy(Stack *s) {
if (!s) return;
free(s->data);
s->data = NULL;
s->capacity = 0;
s->size = 0;
}
int stack_push(Stack *s, int value) {
if (!s) return -1;
if (s->size >= s->capacity) {
size_t new_cap = s->capacity * 2;
int *p = realloc(s->data, new_cap * sizeof(int));
if (!p) return -1;
s->data = p;
s->capacity = new_cap;
}
s->data[s->size++] = value;
return 0;
}
int stack_pop(Stack *s, int *out) {
if (!s || stack_is_empty(s)) return -1;
if (out) *out = s->data[--s->size];
return 0;
}
int stack_peek(const Stack *s, int *out) {
if (!s || stack_is_empty(s) || !out) return -1;
*out = s->data[s->size - 1];
return 0;
}
int stack_is_empty(const Stack *s) {
return !s || s->size == 0;
}
size_t stack_size(const Stack *s) {
return s ? s->size : 0;
}
tests.c
Unduh berkas/* tests.c -- unit tests for all four data structures.
*
* Compile with:
* gcc -std=c11 -Wall -Wextra -fsanitize=address,undefined \
* tests.c linked_list.c stack.c queue.c hash_table.c -o test_runner
*/
#include "linked_list.h"
#include "stack.h"
#include "queue.h"
#include "hash_table.h"
#include <stdio.h>
#include <string.h>
static int tests_passed = 0;
static int tests_failed = 0;
#define ASSERT(cond, msg) do { \
if (cond) { tests_passed++; } \
else { tests_failed++; printf("FAIL: %s (line %d)\n", msg, __LINE__); } \
} while (0)
static void test_linked_list(void) {
LinkedList list;
ll_init(&list);
ASSERT(ll_size(&list) == 0, "ll empty size");
ll_push_back(&list, 10);
ll_push_back(&list, 20);
ll_push_front(&list, 5);
ASSERT(ll_size(&list) == 3, "ll size after push");
int v;
ASSERT(ll_get(&list, 0, &v) == 0 && v == 5, "ll get(0) == 5");
ASSERT(ll_get(&list, 2, &v) == 0 && v == 20, "ll get(2) == 20");
ASSERT(ll_find(&list, 10) == 1, "ll find(10) == 1");
ASSERT(ll_find(&list, 99) == -1, "ll find(99) not found");
ASSERT(ll_pop_front(&list, &v) == 0 && v == 5, "ll pop_front == 5");
ASSERT(ll_size(&list) == 2, "ll size after pop");
ll_remove(&list, 0);
ASSERT(ll_size(&list) == 1, "ll size after remove");
ll_destroy(&list);
ASSERT(ll_size(&list) == 0, "ll size after destroy");
}
static void test_stack(void) {
Stack s;
ASSERT(stack_init(&s, 2) == 0, "stack_init ok");
ASSERT(stack_is_empty(&s), "stack empty initially");
ASSERT(stack_push(&s, 100) == 0, "stack_push 100");
ASSERT(stack_push(&s, 200) == 0, "stack_push 200");
/* Force a grow. */
ASSERT(stack_push(&s, 300) == 0, "stack_push 300 (grow)");
ASSERT(stack_size(&s) == 3, "stack size 3");
int v;
ASSERT(stack_peek(&s, &v) == 0 && v == 300, "stack_peek == 300");
ASSERT(stack_pop(&s, &v) == 0 && v == 300, "stack_pop == 300");
ASSERT(stack_pop(&s, &v) == 0 && v == 200, "stack_pop == 200");
ASSERT(stack_pop(&s, &v) == 0 && v == 100, "stack_pop == 100");
ASSERT(stack_is_empty(&s), "stack empty at end");
ASSERT(stack_pop(&s, &v) == -1, "stack_pop on empty returns -1");
stack_destroy(&s);
}
static void test_queue(void) {
Queue q;
ASSERT(queue_init(&q, 4) == 0, "queue_init ok");
ASSERT(queue_is_empty(&q), "queue empty initially");
for (int i = 1; i <= 4; i++) {
ASSERT(queue_enqueue(&q, i * 10) == 0, "queue_enqueue");
}
ASSERT(queue_size(&q) == 4, "queue full size");
int v;
ASSERT(queue_dequeue(&q, &v) == 0 && v == 10, "queue_dequeue 10");
ASSERT(queue_dequeue(&q, &v) == 0 && v == 20, "queue_dequeue 20");
ASSERT(queue_dequeue(&q, &v) == 0 && v == 30, "queue_dequeue 30");
ASSERT(queue_dequeue(&q, &v) == 0 && v == 40, "queue_dequeue 40");
ASSERT(queue_is_empty(&q), "queue empty after drain");
ASSERT(queue_dequeue(&q, &v) == -1, "queue_dequeue on empty");
queue_destroy(&q);
}
static void test_hash_table(void) {
HashTable ht;
ASSERT(ht_init(&ht, 4) == 0, "ht_init ok");
ASSERT(ht_size(&ht) == 0, "ht empty");
ASSERT(ht_put(&ht, "alpha", 1) == 0, "ht_put alpha");
ASSERT(ht_put(&ht, "beta", 2) == 0, "ht_put beta");
ASSERT(ht_put(&ht, "gamma", 3) == 0, "ht_put gamma");
ASSERT(ht_size(&ht) == 3, "ht size 3");
int v;
ASSERT(ht_get(&ht, "beta", &v) == 0 && v == 2, "ht_get beta");
ASSERT(ht_get(&ht, "missing", &v) == -1, "ht_get missing");
/* Update existing key. */
ASSERT(ht_put(&ht, "beta", 22) == 0, "ht_put beta update");
ASSERT(ht_get(&ht, "beta", &v) == 0 && v == 22, "ht_get beta updated");
ASSERT(ht_size(&ht) == 3, "ht size unchanged after update");
ASSERT(ht_remove(&ht, "alpha") == 0, "ht_remove alpha");
ASSERT(ht_get(&ht, "alpha", &v) == -1, "ht_get alpha gone");
ASSERT(ht_size(&ht) == 2, "ht size 2 after remove");
ht_destroy(&ht);
}
int main(void) {
test_linked_list();
test_stack();
test_queue();
test_hash_table();
printf("\n=== Test Results ===\n");
printf("Passed: %d\n", tests_passed);
printf("Failed: %d\n", tests_failed);
if (tests_failed == 0) {
printf("Lulus : %d\n", tests_passed);
return 0;
}
return 1;
}