malloc adalah fungsi alokator paling dasar di C. Nama singkatnya—memory allocation—menggambarkan tugas tunggalnya: memesan sejumlah byte di heap dan mengembalikan pointer ke blok tersebut. Tidak seperti automatic storage yang otomatis dihapus saat block keluar, memori dari malloc tetap hidup sampai kita secara eksplisit memanggil free. Inilah alat utama yang kita pakai untuk struktur data dinamis seperti array berukuran runtime, linked list, tree, dan buffer besar. Sayangnya kekuatan ini datang dengan tanggung jawab—malloc tidak men-zero-kan memori, tidak memeriksa apakah kita mengakses di luar batas, dan tidak otomatis membebaskan blok. Pada chapter ini kita akan mempelajari kontrak malloc secara benar, mulai dari pola alokasi-pakai-bebaskan hingga bukti empiris bahwa malloc memang meninggalkan garbage.

Kontrak Fungsi malloc #

Fungsi malloc dideklarasikan di <stdlib.h> (ISO/IEC 9899 §7.22.3):

c
void *malloc(size_t size);

Kontraknya adalah:

  • Mengembalikan NULL bila gagal — ketika heap tidak punya cukup memori, malloc mengembalikan NULL. Programmer wajib memeriksa.
  • Mengembalikan pointer ke blok size byte — bila size adalah 0, perilaku implementation-defined (boleh mengembalikan NULL atau pointer non-NULL yang tidak boleh di-dereference).
  • Isi blok tidak terdefinisi — malloc tidak men-zero-kan memori. Byte-byte di dalamnya berisi garbage dari blok yang sebelumnya dipakai dan sudah di-free.
  • Alignment cukup untuk tipe apa pun — pointer yang dikembalikan selalu memiliki alignment yang cocok untuk standard type. Pada x86-64 ini berarti kelipatan 16 byte.

Mekanisme kerja: malloc memanggil allocator (ptmalloc2 di glibc). Allocator memelihara free list blok-blok bebas. Saat permintaan datang, allocator mencari blok yang cukup besar, memecah bila perlu, dan menyisipkan metadata bookkeeping di depan pointer yang dikembalikan—biasanya 8 atau 16 byte sebelum alamat pengguna. Inilah sebabnya malloc(1) sebenarnya memakai puluhan byte di heap.

Visualisasi Konsep #

Diagram berikut memperlihatkan alur lengkap siklus hidup sebuah blok malloc dari permintaan hingga pembebasan:

Pada diagram di atas, alur dimulai dari pemanggilan malloc(n). Bila allocator tidak menemukan blok cukup besar, malloc mengembalikan NULL dan programmer wajib memeriksa sebelum dereference. Bila berhasil, pointer dikembalikan ke blok yang berisi garbage (nilai sisa dari penggunaan heap sebelumnya), sehingga programmer harus mengisi blok sebelum membacanya. Setelah selesai dipakai, free(p) mengembalikan blok ke free list, dan konvensi p = NULL mencegah pointer menjadi dangling. Setiap tahap kecuali E dan F berada di luar kendali programmer—programmer hanya bertanggung jawab atas pengecekan NULL, inisialisasi isi, dan pemanggilan free.

Pola Standar: Alokasi, Pakai, Bebaskan #

Pola aman yang harus selalu kita pakai adalah: alokasikan, periksa NULL, gunakan, lalu panggil free dan set pointer ke NULL.

c
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n = 5;
    int *arr = malloc(n * sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "Gagal mengalokasikan memori\n");
        return 1;
    }

    /* Isi nilai */
    for (int i = 0; i < n; i++) {
        arr[i] = (i + 1) * 10;
    }

    /* Cetak isi */
    printf("Isi array (n = %d):\n", n);
    for (int i = 0; i < n; i++) {
        printf("  arr[%d] = %d\n", i, arr[i]);
    }

    free(arr);
    arr = NULL;
    return 0;
}

Output yang Dihasilkan:

text
Isi array (n = 5):
  arr[0] = 10
  arr[1] = 20
  arr[2] = 30
  arr[3] = 40
  arr[4] = 50

Setelah alokasi, kita menulis nilai ke setiap elemen sebelum membacanya—ini wajib karena malloc tidak menjamin isi awal. Baris free(arr) mengembalikan blok ke allocator, dan arr = NULL menghapus dangling pointer.

Mari kita telaah baris-baris kode di atas:

  1. Baris 6 (int n = 5;): ukuran array ditentukan saat runtime. Inilah keunggulan malloc dibanding deklarasi int arr[5] yang ukurannya harus konstanta kompilasi (kecuali VLA).
  2. Baris 7 (int *arr = malloc(n * sizeof(int));): mengalokasikan n * sizeof(int) byte. Idioma n * sizeof(*T) lebih aman daripada (T*)malloc(n * sizeof(T)) karena menghindari cast yang bisa menyembunyikan bug lupa #include <stdlib.h> di C.
  3. Baris 8 (if (arr == NULL)): pengecekan NULL wajib. Bila malloc gagal, arr bernilai NULL dan dereference di baris berikutnya akan menyebabkan segfault atau undefined behavior.
  4. Baris 13-15 (arr[i] = (i + 1) * 10;): menulis nilai sebelum membaca. Tanpa inisialisasi ini, isi arr[i] tidak terdefinisi—bisa apa saja.
  5. Baris 23 (free(arr);): mengembalikan blok ke allocator. Setelah baris ini, arr menjadi dangling—alamat masih disimpan, tetapi memori sudah tidak lagi dimiliki program.
  6. Baris 24 (arr = NULL;): menghapus dangling pointer sehingga pengecekan if (arr) selanjutnya menjadi false.

Bukti Empiris: malloc Tidak Men-zero-kan Memori #

Banyak pemula mengira malloc mengembalikan blok berisi nol. Standar C justru menyatakan kebalikannya: “The contents of the allocated space are indeterminate” (§7.22.3.4). Kita bisa membuktikannya dengan mengalokasikan blok, menulis nilai, membebaskannya, lalu mengalokasikan blok lain berukuran sama.

c
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    /* Tahap 1: tulis data ke blok heap lalu free */
    int *arr1 = malloc(5 * sizeof(int));
    if (!arr1) return 1;
    for (int i = 0; i < 5; i++) {
        arr1[i] = (i + 1) * 100;   /* 100, 200, 300, 400, 500 */
    }
    printf("arr1 sebelum free: %d %d %d %d %d\n",
           arr1[0], arr1[1], arr1[2], arr1[3], arr1[4]);
    free(arr1);

    /* Tahap 2: malloc baru - allocator sering memakai ulang blok yang sama */
    int *arr2 = malloc(5 * sizeof(int));
    if (!arr2) return 1;
    printf("arr2 setelah malloc (belum diisi): %d %d %d %d %d\n",
           arr2[0], arr2[1], arr2[2], arr2[3], arr2[4]);

    free(arr2);
    return 0;
}

Output yang Dihasilkan:

text
arr1 sebelum free: 100 200 300 400 500
arr2 setelah malloc (belum diisi): 1509142472 5 0 0 500

Perhatikan output arr2: nilai 1509142472 di indeks 0 dan 5 di indeks 1 adalah garbage dari penggunaan heap sebelumnya. Nilai 500 di indeks 4 bahkan merupakan sisa dari arr1[4] yang ditulis pada tahap 1—bukti bahwa allocator glibc memakai ulang blok yang sama persis. Pola ini tidak deterministik; output berbeda pada setiap eksekusi dan pada setiap mesin.