Menu

calloc dan realloc di C: Memori Bernilai Nol dan Memperbesar Array

calloc memberimu memori yang sudah bernilai nol dan mengalikan ukurannya dengan aman; realloc memperbesar blok yang sudah kamu isi. Halaman ini membahas keduanya, pola pointer sementara yang mencegah realloc yang gagal menyebabkan kebocoran, dan kapan calloc lebih baik daripada malloc ditambah memset.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

malloc menjawab satu pertanyaan: beri aku byte sebanyak ini. Dua pendampingnya di <stdlib.h> menjawab pertanyaan yang muncul berikutnya: beri aku byte sebanyak ini, dalam keadaan bersih (calloc), dan aku butuh blok yang sudah kumiliki jadi lebih besar (realloc).

calloc: Jumlah, Ukuran, dan Nol

void *calloc(size_t count, size_t size);

Dua argumen, bukan satu, dan hasilnya diisi dengan byte nol.

Itulah kasus klasiknya: histogram atau array penghitung di mana setiap slot harus mulai dari nol. Dengan malloc kamu perlu perulangan; calloc melakukannya sebagai bagian dari alokasi, sering kali tanpa biaya tambahan karena sistem operasi memang membagikan halaman memori baru yang sudah bernilai nol.

Alasan overflow

Pemisahan menjadi dua argumen bukan sekadar kosmetik. Bayangkan jumlah yang dibaca dari sebuah file:

size_t n = huge_value_from_input;

int *a = malloc(n * sizeof *a);     /* the product can wrap around */
int *b = calloc(n, sizeof *b);      /* required to detect the overflow */

Kalau n * sizeof *a melebihi kapasitas size_t, hasilnya melingkar menjadi angka kecil, malloc berhasil dengan blok yang mungil, dan setiap penulisan berikutnya berjalan jauh melewati ujungnya. Standar mewajibkan calloc untuk gagal dan mengembalikan NULL dalam kasus ini. Kalau ukuran berasal dari luar programmu, pemeriksaan itu layak dimiliki.

Apa arti "bernilai nol" sebenarnya

calloc menulis byte nol. Untuk integer dan karakter, itu berarti nilai nol, persis yang kamu inginkan. Untuk pointer dan nilai floating-point, semua bit nol berarti null pointer dan 0.0 di semua platform umum, tetapi standar C tidak menjaminnya. Kode yang harus benar-benar portabel meng-assign nilai-nilai itu secara eksplisit.

calloc vs malloc + memset

int *a = calloc(n, sizeof *a);

int *b = malloc(n * sizeof *b);
if (b != NULL) {
    memset(b, 0, n * sizeof *b);
}

Hasilnya sama. Utamakan calloc: cukup satu baris, ada pemeriksaan overflow, dan untuk blok besar ia bisa sama sekali tidak menyentuh memorinya. Utamakan malloc biasa kalau kamu toh akan menimpa setiap byte: mengisi nol satu megabyte yang akan langsung kamu isi lagi hanya buang-buang tenaga.

realloc: Mengubah Ukuran

void *realloc(void *p, size_t newsize);

realloc mengembalikan blok berukuran newsize byte yang isinya sama dengan blok lama sampai ukuran yang lebih kecil dari keduanya. Blok bisa diperpanjang di tempatnya, atau dialokasikan baru, disalin, lalu blok lama di-free. Kamu tidak bisa tahu yang mana, jadi satu-satunya pointer yang boleh kamu pakai setelahnya adalah pointer yang dikembalikan.

Pola pointer tmp

Tiga baris di sekitar tmp itulah alasan fungsi ini terkenal rawan. Jalan pintas yang menggoda justru salah:

/* BUG: leaks the original block when realloc fails */
p = realloc(p, newsize);
if (p == NULL) {
    return;
}

Saat realloc gagal, ia mengembalikan NULL dan membiarkan blok asli tetap teralokasi tanpa perubahan. Meng-assign hasilnya langsung ke p menimpa satu-satunya pointer ke blok itu, sehingga blok tersebut tidak akan pernah bisa di-free. Itu kebocoran memori, dan terjadinya tepat saat memori sedang langka. Assign ke tmp, uji tmp, baru simpan:

void *tmp = realloc(p, newsize);
if (tmp == NULL) {
    /* p is still valid; handle the failure however suits the caller */
    return 0;
}
p = tmp;

Dua perilaku lain yang perlu diketahui: realloc(NULL, n) berperilaku persis seperti malloc(n), sehingga fungsi pemerbesar bisa menangani pemanggilan pertamanya tanpa kasus khusus. Lalu memperbesar blok membiarkan byte baru tidak diinisialisasi: hanya isi lama yang dipertahankan.

Memperbesar Array: Pola Standar

Berikut pola yang dipakai hampir setiap container C: panjang, kapasitas, dan menggandakan kapasitas saat keduanya bertemu.

Ada dua keputusan desain yang menjadi tumpuan di sini.

Digandakan, bukan ditambah satu. Memperbesar dengan jumlah tetap membuat setiap push menyalin seluruh array, sehingga mengisi n elemen memakan kira-kira n² penyalinan byte. Menggandakan membuat penyalinan cukup jarang sehingga rata-rata setiap push berjalan dalam waktu konstan. Awal cap == 0 ? 4 : cap * 2 menangani push pertama tanpa cabang terpisah, karena realloc(NULL, n) hanyalah malloc.

Struct memiliki satu alokasi. v.data di-free tepat satu kali, oleh siapa pun yang memiliki struct itu. Menuliskan aturan kepemilikan itu, entah di komentar atau dengan memasangkan setiap vec_init dengan vec_free, adalah yang menjaga container yang terus membesar agar tidak bocor saat berpindah antarfungsi.

Memperkecil

realloc dengan ukuran yang lebih kecil memangkas sebuah blok, yang berguna setelah kamu membaca data yang jumlahnya tidak diketahui ke dalam buffer yang kebesaran:

Memperkecil yang gagal bukan error yang perlu sampai membatalkan program: blok lama yang lebih besar masih valid dan masih benar. Inilah satu-satunya kasus di mana mengabaikan kegagalan adalah keputusan yang tepat. Tetapi perhatikan bahwa kode ini tetap lewat tmp, karena aturan untuk tidak menimpa pointer yang masih aktif tidak berubah.

Mana yang Dipakai

Yang kamu inginkanGunakan
Byte, isinya tidak pentingmalloc(n * sizeof *p)
Sejumlah item, diisi nolcalloc(n, sizeof *p)
Jumlah dari input yang tidak tepercayacalloc, untuk pemeriksaan overflow
Blok yang sudah ada, diperbesar atau diperkecilrealloc lewat pointer tmp
Alokasi pertama di perulangan pemerbesarrealloc(NULL, n), tanpa kasus khusus

Keempatnya dilepaskan dengan free yang sama, tepat satu kali.

Kesalahan Umum

  • p = realloc(p, n): bocor saat gagal. Selalu lewat tmp.
  • Menyimpan pointer lama ke dalam blok. Setelah realloc, datanya mungkin sudah pindah, jadi setiap pointer ke bagian dalam blok atau alamat yang diturunkan dari indeks yang tersimpan sudah usang. Indeks tetap berlaku setelah pemindahan; pointer tidak.
  • Menganggap byte baru bernilai nol. Hanya calloc yang mengisi nol. realloc membiarkan area tambahan tidak diinisialisasi.
  • calloc(n * size, 1): ini membuang pemeriksaan overflow yang menjadi alasan bentuk dua argumen itu ada.
  • Mem-free pointer lama setelah realloc berhasil. realloc sudah melepaskannya; free kedua adalah double free.

Pertanyaan yang sering diajukan

Apa perbedaan calloc dan malloc di C?

Ada dua perbedaan. calloc(n, size) menerima jumlah dan ukuran elemen secara terpisah, lalu memeriksa bahwa hasil perkaliannya tidak overflow, sedangkan malloc(n * size) bisa melingkar diam-diam. Selain itu, calloc mengisi blok dengan nol, sementara malloc membiarkannya berisi apa pun yang ada sebelumnya.

Bagaimana cara kerja realloc di C?

realloc(p, newsize) mengembalikan blok berukuran baru dengan isi lamamu tetap terjaga sampai ukuran yang lebih kecil dari keduanya. Blok bisa diperbesar di tempat atau dipindahkan, jadi pointer yang dikembalikan adalah satu-satunya yang boleh terus kamu pakai: pointer lama mungkin sudah di-free.

Kenapa hasil realloc harus di-assign ke pointer sementara?

Karena p = realloc(p, n) menimpa p dengan NULL saat pemanggilan gagal, padahal blok aslinya masih teralokasi tanpa ada yang menunjuk ke sana: kebocoran memori. Assign dulu ke tmp, periksa apakah NULL, baru setelah itu tulis p = tmp.

Kapan sebaiknya memakai calloc, bukan malloc?

Saat kamu benar-benar butuh memori bernilai nol (array penghitung, struct yang field-nya harus mulai kosong, buffer yang hanya akan diisi sebagian) atau saat ukurannya adalah jumlah dikali ukuran elemen yang bisa besar. Kalau kamu toh akan menimpa setiap byte, malloc melewatkan pekerjaan mengisi nol itu.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai