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:

AspekStackHeap
AlokasiOtomatis oleh compilerManual lewat malloc/calloc/realloc
DealokasiOtomatis saat fungsi kembaliManual lewat free
Ukuran tipikal1-8 MB (Linux default 8 MB)Dibatasi RAM/swap sistem
KecepatanSangat cepat (cukup menggeser stack pointer)Lebih lambat (allocator perlu mencari blok bebas)
LifetimeHingga fungsi pemilik kembaliHingga free dipanggil
Arah pertumbuhanKe alamat rendahKe alamat tinggi
FragmentasiTidak 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.

c
#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:

text
Stack: sum = 499999500000, waktu = 0.006933 detik
Heap : sum = 499999500000, waktu = 0.007745 detik

Selisih 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:

  1. Baris 7 (double pakai_stack(void)): fungsi yang mengalokasikan arr secara lokal di stack. Saat fungsi dipanggil, stack pointer turun sebesar N * sizeof(int) = 4.000.000 byte.
  2. Baris 8 (int arr[N];): deklarasi variable-length array (VLA) bila N bukan konstanta; di sini N adalah #define sehingga bersifat konstanta pra-proses. Array ini tidak diinisialisasi ke nol.
  3. Baris 17 (double pakai_heap(void)): fungsi yang meminta memori dari heap. Tidak seperti stack, blok ini tidak hilang saat fungsi kembali—arr tetap valid sampai free dipanggil.
  4. Baris 18 (int *arr = malloc(N * sizeof(int));): alokasi 4.000.000 byte di heap. malloc mengembalikan void* yang otomatis di-promote ke int* di C (berbeda di C++).
  5. Baris 19 (if (!arr) return -1.0;): pengecekan NULL—jika alokasi gagal, fungsi mengembalikan nilai sentinel -1.0. Pengecekan ini wajib karena malloc boleh mengembalikan NULL ketika memori tidak cukup.
  6. Baris 27 (free(arr);): dealokasi manual. Lupa memanggil free menyebabkan memory leak—topik yang akan kita dalami di chapter khusus.
  7. Baris 35 (clock()): fungsi dari <time.h> yang mengembalikan CPU time sejak program dimulai. Selisih t2 - t1 dibagi CLOCKS_PER_SEC menghasilkan 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.

c
#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:

text
0 1 2 3 4

Kompiler memberi peringatan berikut untuk fungsi buat_array_stack:

text
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.