Topik ini membahas empat fungsi pustaka standar yang mengatur seluruh blok heap, yaitu malloc untuk memesan, calloc untuk memesan sekaligus mengisi nol, realloc untuk mengubah ukuran, dan free untuk melepas. Pembahasannya mencakup pola pemanggilan masing-masing, pemeriksaan kegagalan yang wajib mengiringi setiap panggilan alokasi, serta aturan kepemilikan yang menjaga setiap blok dibebaskan tepat satu kali. Setelah topik ini dipahami, pemesanan memori tidak lagi berupa resep yang disalin, melainkan pola dengan alasan yang jelas pada tiap langkahnya.
Memesan blok dengan malloc#
Fungsi malloc menerima satu argumen berupa jumlah byte yang diminta dan mengembalikan alamat awal blok tersebut dalam bentuk pointer generik void *. Alamat itu dapat disimpan pada pointer bertipe apa pun tanpa cast, misalnya int *data = malloc(n * sizeof(int)); untuk sebuah array dengan n elemen. Isi blok yang baru dipesan tidak dijamin berarti apa pun, sehingga blok harus diisi lebih dahulu sebelum dibaca. Idiom n * sizeof(int) menjaga perhitungan byte tetap benar meskipun tipe datanya nanti diganti.
Program berikut memesan blok untuk sepuluh bilangan bulat, mengisinya dengan kuadrat, lalu membaca dua elemennya sebelum membebaskan bloknya.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *data = malloc(10 * sizeof(int));
if (data == NULL) {
printf("Alokasi gagal.\n");
return 1;
}
for (int i = 0; i < 10; i = i + 1) {
data[i] = (i + 1) * (i + 1);
}
printf("data[3] = %d\n", data[3]);
printf("data[9] = %d\n", data[9]);
printf("Ukuran blok: %zu byte\n", 10 * sizeof(int));
free(data);
printf("Blok telah dibebaskan.\n");
return 0;
}data[3] = 16
data[9] = 100
Ukuran blok: 40 byte
Blok telah dibebaskan.Setelah blok dipesan, pengaksesannya sama persis dengan array biasa, yaitu lewat indeks data[i], karena array biasa dan blok malloc sama-sama menyajikan kumpulan elemen berurutan. Perbedaannya terletak pada pemesanan dan pembebasan, dan keduanya kini menjadi tanggung jawab kode. Pemanggilan free di akhir mengembalikan blok kepada pustaka standar agar bisa dipakai lagi oleh pemesanan berikutnya.
Keempat fungsi membentuk satu siklus hidup yang sama untuk setiap blok, yaitu dipesan lewat malloc atau calloc, digunakan lewat pointer, lalu dilepas lewat free. Diagram berikut merangkum siklus tersebut sebelum tiap fungsinya dibahas satu per satu.
Memeriksa kegagalan dengan NULL#
Fungsi alokasi dapat gagal, dan kegagalannya dilaporkan berupa NULL, bukan error yang menghentikan program. Permintaan bisa gagal karena memori sistem habis, ukuran permintaan terlalu besar, atau batas lingkungan terlampaui. Karena itu pemeriksaan NULL bukan pelengkap formal, melainkan bagian dari pola dasar setiap panggilan malloc, calloc, dan realloc. Apabila pemeriksaan dilewati dan alokasi ternyata gagal, penulisan pertama ke blok akan mengakses memori melalui alamat kosong dan menghentikan program.
Program berikut meminta satu blok dengan ukuran yang mustahil dilayani, yaitu nilai terbesar yang dapat ditampung tipe size_t. Permintaan tersebut pasti ditolak, sehingga jalur pemeriksaan NULL benar-benar terjadi dan program dapat meresponsnya dengan tenang.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
size_t ukuran_tak_masuk_akal = (size_t)-1;
int *data = malloc(ukuran_tak_masuk_akal);
if (data == NULL) {
printf("Permintaan %zu byte ditolak: malloc mengembalikan NULL\n", ukuran_tak_masuk_akal);
printf("Program tetap berjalan karena kegagalan diperiksa.\n");
return 0;
}
free(data);
return 0;
}Permintaan 18446744073709551615 byte ditolak: malloc mengembalikan NULL
Program tetap berjalan karena kegagalan diperiksa.Angka pada keluaran adalah nilai terbesar tipe size_t pada sistem 64 bit, yaitu sekitar delapan belas kuadriliun byte, dan permintaan sebesar itu jelas melampaui seluruh memori yang ada. Yang penting dari contoh ini bukan angkanya, melainkan kelanjutannya, yaitu program tetap berjalan dan berakhir dengan rapi. Respons terhadap kegagalan bisa berupa pesan error, nilai kembali yang menandakan kegagalan, atau pengecilan ukuran permintaan, sesuai kebutuhan programnya.
calloc: blok yang diisi nol#
Fungsi calloc memesan blok untuk beberapa elemen sekaligus dan mengisi seluruhnya dengan nol sebelum mengembalikan alamatnya. Bentuk pemanggilannya adalah calloc(banyak_elemen, ukuran_elemen), sehingga perhitungan byte dikerjakan di dalam fungsinya. Fungsi ini cocok untuk penampung yang nilainya dihitung bertahap, misalnya penghitung frekuensi atau larik pointer yang berawal dari keadaan kosong. Dengan calloc, keadaan awal nol menjadi bagian dari kontrak fungsinya, bukan pekerjaan tambahan yang bisa terlupa.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *nol = calloc(4, sizeof(int));
if (nol == NULL) {
return 1;
}
printf("Isi blok calloc : %d %d %d %d\n", nol[0], nol[1], nol[2], nol[3]);
printf("Ukuran blok : %zu byte\n", 4 * sizeof(int));
free(nol);
return 0;
}Isi blok calloc : 0 0 0 0
Ukuran blok : 16 byteKeempat elemen tercetak 0 tanpa satu baris pengisian pun, karena penyeragaman itu dilakukan calloc. Kontrasnya dengan malloc perlu ditegaskan, yaitu isi blok malloc belum tentu berarti sehingga membacanya sebelum diisi adalah kesalahan. Untuk array bilangan bulat berukuran kecil, perbedaan keduanya terasa tipis, tetapi pada penampung besar atau larik pointer, calloc menghemat satu perulangan inisialisasi yang mudah terlewat.
Keadaan nol itu punya arti yang lebih dalam pada larik pointer, karena nol pada pointer berarti NULL, sehingga larik hasil calloc langsung berada pada keadaan kosong yang sah. Dengan malloc, larik semacam itu berisi alamat sampah, dan satu pembacaan saja sudah cukup untuk membuat program berperilaku aneh. Aturan praktisnya sederhana, yaitu pilih calloc setiap kali keadaan awal yang nol memang dimaksudkan, dan pilih malloc apabila bloknya pasti diisi penuh segera setelah dipesan.
realloc: mengubah ukuran blok#
Fungsi realloc mengubah ukuran blok yang sudah ada, baik memperbesar maupun memperkecil, tanpa menghapus isinya. Isi lama dipertahankan sebatas ukuran yang lebih kecil di antara ukuran lama dan ukuran baru, sedangkan ruang tambahan pada blok yang diperbesar belum berisi apa pun. Perlu diketahui bahwa realloc boleh memindahkan blok ke alamat lain ketika ruang di tempat lama tidak cukup, sehingga hasilnya wajib ditampung dan dipakai sebagai alamat terbaru.
#include <stdio.h>
#include <stdlib.h>
void cetak(const char *label, const int *data, int jumlah)
{
printf("%s:", label);
for (int i = 0; i < jumlah; i = i + 1) {
printf(" %d", data[i]);
}
printf("\n");
}
int main(void)
{
int kapasitas = 3;
int jumlah = 0;
int *nilai = malloc((size_t)kapasitas * sizeof(int));
if (nilai == NULL) {
return 1;
}
int awal[3] = {70, 85, 90};
for (int i = 0; i < 3; i = i + 1) {
nilai[jumlah] = awal[i];
jumlah = jumlah + 1;
}
cetak("Kapasitas 3", nilai, jumlah);
int tambahan[2] = {60, 95};
int *sementara = realloc(nilai, 5 * sizeof(int));
if (sementara == NULL) {
free(nilai);
return 1;
}
nilai = sementara;
for (int i = 0; i < 2; i = i + 1) {
nilai[jumlah] = tambahan[i];
jumlah = jumlah + 1;
}
cetak("Kapasitas 5", nilai, jumlah);
free(nilai);
return 0;
}Kapasitas 3: 70 85 90
Kapasitas 5: 70 85 90 60 95Nilai yang diisi sebelum perluasan tetap utuh setelah blok berubah ukuran, dan dua nilai tambahan menempati ruang barunya. Pola pointer sementara pada contoh di atas ada karena satu alasan penting berikut.
// tidak-dijalankan
nilai = realloc(nilai, ukuran_baru); /* bila gagal, alamat lama hilang */Satu konsekuensi lain dari kemungkinan pemindahan itu juga perlu diwaspadai, yaitu setiap pointer lain yang masih menyimpan alamat lama menjadi usang begitu realloc memindahkan blok. Salin ulang alamat terbaru kepada seluruh pemegangnya setelah perluasan, atau batasi kepemilikan blok pada satu pointer saja. Perluasan yang memperkecil blok tidak memindahkan isi yang tersisa, tetapi data yang terpotong hilang tanpa pesan, sehingga perhitungan ukuran barunya harus dilakukan dengan cermat.
free dan aturan kepemilikan#
Fungsi free menerima alamat blok hasil malloc, calloc, atau realloc lalu mengembalikan blok tersebut kepada pustaka standar. Setiap blok dibebaskan tepat satu kali, karena pembebasan dua kali dan pemakaian setelah pembebasan sama-sama berakibat tidak terdefinisi. Pemanggilan free dengan NULL justru aman dan tidak melakukan apa pun, sehingga pointer yang sudah dibebaskan boleh diisi NULL untuk mencegah pembebasan kedua.
Kepemilikan (ownership) adalah konvensi yang menjawab satu pertanyaan, yaitu fungsi mana yang bertanggung jawab membebaskan sebuah blok. Kepemilikan dapat berpindah lewat nilai kembali, seperti pada pola berikut, dan pemanggil yang menerima alamatnya menjadi pihak yang wajib membebaskannya.
// tidak-dijalankan
int *buat_penampung(size_t jumlah_elemen)
{
int *p = malloc(jumlah_elemen * sizeof(int));
if (p == NULL) {
return NULL; /* kegagalan dilaporkan ke pemanggil */
}
return p; /* pemanggil yang wajib membebaskan */
}Cara lain yang lebih mudah diaudit adalah membebaskan blok pada fungsi yang sama dengan tempat blok itu dipesan. Kedua gaya sama-sama sah sepanjang aturannya konsisten dan tertulis pada komentar atau nama fungsinya. Masalah muncul ketika kepemilikannya samar, misalnya fungsi kadang membebaskan dan kadang tidak tergantung jalur eksekusinya, sehingga pemanggil tidak punya dasar untuk memutuskan. Kebiasaan menegaskan pemilik setiap blok adalah pertahanan paling murah terhadap kebocoran memori dan pembebasan ganda.
Kesalahan umum#
Empat kesalahan berikut paling sering muncul pada pemakaian pertama keluarga malloc, dan semuanya dapat dicegah dengan pola yang sudah dibahas.
- Lupa memeriksa
NULLsebelum memakai blok. Program tampak sehat pada alokasi kecil, lalu berhenti mendadak ketika permintaan besar pertama gagal. Jadikan pemeriksaanNULLbagian tetap dari setiap panggilan alokasi dan tentukan responsnya sejak desain. - Mengisi dan membaca blok sebelum ukurannya dipastikan. Kesalahan umumnya berupa salah hitung byte, misalnya lupa
sizeof, sehingga blok terlalu sempit dan penulisan melewati batasnya. Pertahankan idiomn * sizeof(tipe)agar perhitungannya ikut berubah saat tipe diganti. - Membebaskan alamat yang bukan berasal dari keluarga malloc. Memanggil free pada variabel lokal atau array statis adalah perilaku tak terdefinisi karena blok tersebut tidak pernah dipesan dari heap. Free hanya untuk alamat yang dikeluarkan oleh malloc, calloc, atau realloc.
- Membaca isi blok malloc sebelum diisi. Isi awalnya tidak dijamin berarti, sehingga nilainya bisa tampak benar pada satu eksekusi dan salah pada eksekusi lain. Gunakan calloc ketika keadaan nol diinginkan, atau isi blok lebih dahulu sebelum dibaca.
Ringkasan#
- malloc memesan blok dan mengembalikan alamatnya, dengan isi yang belum berarti sehingga wajib diisi sebelum dibaca.
- Setiap panggilan alokasi dapat gagal dan mengembalikan
NULL, sehingga pemeriksaan kegagalannya menjadi bagian dari pola dasar. - calloc memesan sekaligus mengisi nol, sedangkan realloc mengubah ukuran blok dan dapat memindahkannya ke alamat baru.
- free melepas blok tepat satu kali, dan kepemilikan setiap blok harus jelas pada fungsi mana pun yang memegang alamatnya.
Referensi#
- malloc(3) — Linux man-pages project — Dokumentasi resmi malloc, calloc, realloc, dan free beserta kontrak masing-masing.
- C memory management library — cppreference.com — Ikhtisar pustaka manajemen memori C beserta halaman detail tiap fungsinya.