Stack dan heap adalah dua wilayah memori yang paling sering kita sentuh saat menulis C. Stack dipakai untuk variabel lokal dan parameter fungsi; heap dipakai untuk memori yang kita minta eksplisit lewat malloc, calloc, atau realloc. Walaupun keduanya sama-sama menyimpan byte, sifat keduanya sangat berbeda: stack cepat tetapi kecil dan berumur pendek, heap fleksibel dan besar tetapi harus dikelola manual. Pada chapter ini kita akan membandingkan keduanya secara konkret—mulai dari ukuran, kecepatan, lifetime, hingga jebakan klasik berupa “mengembalikan alamat variabel lokal”.
Perbandingan Singkat Stack vs Heap #
Tabel berikut merangkum perbedaan utama:
| Aspek | Stack | Heap |
|---|---|---|
| Alokasi | Otomatis oleh compiler | Manual lewat malloc/calloc/realloc |
| Dealokasi | Otomatis saat fungsi kembali | Manual lewat free |
| Ukuran tipikal | 1-8 MB (Linux default 8 MB) | Dibatasi RAM/swap sistem |
| Kecepatan | Sangat cepat (cukup menggeser stack pointer) | Lebih lambat (allocator perlu mencari blok bebas) |
| Lifetime | Hingga fungsi pemilik kembali | Hingga free dipanggil |
| Arah pertumbuhan | Ke alamat rendah | Ke alamat tinggi |
| Fragmentasi | Tidak ada (LIFO) | Bisa terjadi |
Mekanisme kerja di balik layar: saat fungsi dipanggil, CPU menambah instruksi prologue yang mengurangi stack pointer dengan ukuran yang dibutuhkan untuk variabel lokal fungsi tersebut. Saat fungsi kembali, epilogue mengembalikan stack pointer ke posisi semula—dealokasi instan tanpa daftar bebas. Sebaliknya, malloc memanggil allocator (misalnya ptmalloc di glibc) yang memelihara free list blok-blok bebas; mencari blok yang pas, memecah bila perlu, lalu mencatat metadata bookkeeping. Itulah sebabnya alokasi heap lebih lambat.
Visualisasi Konsep #
Diagram berikut membandingkan siklus hidup alokasi stack versus heap, beserta perbedaan kecepatan dan lifetime-nya:
Pada diagram di atas, alokasi stack hanya membutuhkan satu instruksi geser stack pointer dan dealokasi instan saat fungsi kembali—lifetime variabel terbatas pada scope fungsi. Sebaliknya, alokasi heap melibatkan allocator yang mencari blok bebas pada free list, menyimpan metadata bookkeeping (8-16 byte di depan pointer pengguna), dan dealokasi terjadi hanya saat free dipanggil—lifetime blok tidak terkait scope fungsi pencipta. Inilah sebabnya stack lebih cepat tetapi kaku, sedangkan heap lebih fleksibel tetapi memerlukan manajemen manual.
Eksperimen Waktu: Stack vs Heap #
Kita ukur perbandaannya secara empiris dengan mengalokasikan array berukuran 1 juta elemen int (4 MB) di kedua wilayah, lalu menjumlahkannya.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define N 1000000
double pakai_stack(void) {
int arr[N]; /* 1 juta int = 4 MB di stack */
for (int i = 0; i < N; i++) {
arr[i] = i;
}
long sum = 0;
for (int i = 0; i < N; i++) {
sum += arr[i];
}
return (double)sum;
}
double pakai_heap(void) {
int *arr = malloc(N * sizeof(int)); /* 4 MB di heap */
if (!arr) return -1.0;
for (int i = 0; i < N; i++) {
arr[i] = i;
}
long sum = 0;
for (int i = 0; i < N; i++) {
sum += arr[i];
}
free(arr);
return (double)sum;
}
int main(void) {
clock_t t1, t2;
t1 = clock();
double s1 = pakai_stack();
t2 = clock();
printf("Stack: sum = %.0f, waktu = %.6f detik\n", s1, (double)(t2 - t1) / CLOCKS_PER_SEC);
t1 = clock();
double s2 = pakai_heap();
t2 = clock();
printf("Heap : sum = %.0f, waktu = %.6f detik\n", s2, (double)(t2 - t1) / CLOCKS_PER_SEC);
return 0;
}Output yang Dihasilkan:
Stack: sum = 499999500000, waktu = 0.006933 detik
Heap : sum = 499999500000, waktu = 0.007745 detikSelisih waktu memang kecil—sekitar 11% lebih lambat untuk heap—karena pada contoh ini biaya dominan adalah pengisian dan penjumlahan array, bukan alokasi itu sendiri. Pada program yang banyak memanggil malloc untuk blok kecil, selisihnya akan jauh lebih besar karena bookkeeping allocator mendominasi.
Mari kita telaah baris-baris kode di atas:
- Baris 7 (
double pakai_stack(void)): fungsi yang mengalokasikanarrsecara lokal di stack. Saat fungsi dipanggil, stack pointer turun sebesarN * sizeof(int)= 4.000.000 byte. - Baris 8 (
int arr[N];): deklarasi variable-length array (VLA) bilaNbukan konstanta; di siniNadalah#definesehingga bersifat konstanta pra-proses. Array ini tidak diinisialisasi ke nol. - Baris 17 (
double pakai_heap(void)): fungsi yang meminta memori dari heap. Tidak seperti stack, blok ini tidak hilang saat fungsi kembali—arrtetap valid sampaifreedipanggil. - Baris 18 (
int *arr = malloc(N * sizeof(int));): alokasi 4.000.000 byte di heap.mallocmengembalikanvoid*yang otomatis di-promote keint*di C (berbeda di C++). - Baris 19 (
if (!arr) return -1.0;): pengecekanNULL—jika alokasi gagal, fungsi mengembalikan nilai sentinel-1.0. Pengecekan ini wajib karenamallocboleh mengembalikanNULLketika memori tidak cukup. - Baris 27 (
free(arr);): dealokasi manual. Lupa memanggilfreemenyebabkan memory leak—topik yang akan kita dalami di chapter khusus. - Baris 35 (
clock()): fungsi dari<time.h>yang mengembalikan CPU time sejak program dimulai. Selisiht2 - t1dibagiCLOCKS_PER_SECmenghasilkan detik.
Jebakan Klasik: Mengembalikan Alamat Variabel Lokal #
Karena variabel lokal hidup di stack dan stack hilang saat fungsi kembali, mengembalikan alamat variabel lokal adalah bug serius. Compiler memberi peringatan eksplisit untuk kasus ini.
#include <stdio.h>
#include <stdlib.h>
int *buat_array_heap(int n) {
int *arr = malloc(n * sizeof(int));
/* Alokasi heap bertahan setelah fungsi kembali */
return arr;
}
int *buat_array_stack(int n) {
int arr[10]; /* variabel lokal di stack */
for (int i = 0; i < n && i < 10; i++) arr[i] = i;
/* BUG: mengembalikan alamat stack yang akan hilang */
return arr;
}
int main(void) {
int *h = buat_array_heap(5);
if (h) {
for (int i = 0; i < 5; i++) printf("%d ", h[i]);
printf("\n");
free(h);
}
return 0;
}Output yang Dihasilkan:
0 1 2 3 4Kompiler memberi peringatan berikut untuk fungsi buat_array_stack:
test_stack_heap2.c:12:12: warning: function returns address of local variable [-Wreturn-local-addr]
12 | return arr; /* BUG: mengembalikan alamat stack yang akan hilang */
| ^~~Pada main, kita sengaja hanya memanggil buat_array_heap karena versi stack akan menghasilkan undefined behavior saat dipakai. Inilah inti perbedaan lifetime: data di heap tetap valid melampaui scope fungsi yang menciptakannya, sedangkan data di stack tidak.