realloc adalah fungsi yang memperbesar atau memperkecil blok memori yang sudah dialokasikan oleh malloc/calloc. Tanpa realloc, satu-satunya cara mengubah ukuran array dinamis adalah mengalokasikan blok baru, menyalin data, dan membebaskan blok lama—tepat seperti yang realloc lakukan secara internal. Yang istimewa, realloc sering bisa memperbesar blok in-place (di tempat yang sama) sehingga tidak perlu menyalin data; tetapi bila tidak bisa, realloc mengalokasikan blok baru, menyalin data lama, dan membebaskan blok lama—semua dalam satu panggilan. Pada chapter ini kita akan mempelajari kontrak realloc, menggunakannya untuk menumbuhkan array, dan membuktikan bahwa pointer lama boleh berubah.

Kontrak Fungsi realloc #

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

c
void *realloc(void *ptr, size_t size);

Kontraknya:

  • ptr menunjuk blok yang sebelumnya dialokasikan oleh malloc, calloc, atau realloc. Bila ptr adalah NULL, realloc berperilaku seperti malloc(size).
  • size adalah ukuran baru dalam byte. Bila size adalah 0 dan ptr bukan NULL, perilaku implementation-defined; glibc membebaskan blok dan mengembalikan NULL.
  • Mengembalikan pointer baru — bisa sama dengan ptr (perbesaran in-place) atau berbeda (blok dipindahkan). Data lama hingga min(ukuran_lama, ukuran_baru) dijamin tetap utuh.
  • Bila realloc gagal — mengembalikan NULL, dan blok lama tetap valid serta tidak dibebaskan. Programmer tetap harus memanggil free(ptr) nantinya.
  • Isi byte baru di luar ukuran lama tidak terdefinisi — realloc tidak men-zero-kan wilayah baru (berbeda dengan calloc).

Pola klasik adalah memiliki array dengan kapasitas yang bisa tumbuh. Kita alokasikan kapasitas awal, lalu panggil realloc ketika data baru tidak muat.

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

int main(void) {
    size_t kapasitas = 3;
    int *arr = malloc(kapasitas * sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "malloc gagal\n");
        return 1;
    }

    /* Isi 3 elemen awal */
    for (size_t i = 0; i < kapasitas; i++) {
        arr[i] = (int)(i * 10);
    }

    printf("Sebelum realloc: kapasitas=%zu, alamat=%p\n",
           kapasitas, (void*)arr);
    for (size_t i = 0; i < kapasitas; i++) {
        printf("  arr[%zu] = %d\n", i, arr[i]);
    }

    /* Simpan alamat lama untuk dibandingkan */
    int *alamat_lama = arr;

    /* Tumbuhkan array dari 3 ke 6 elemen */
    size_t kapasitas_baru = 6;
    int *arr_baru = realloc(arr, kapasitas_baru * sizeof(int));
    if (arr_baru == NULL) {
        /* realloc gagal -- arr LAMA tetap valid, kita harus free manual */
        free(arr);
        fprintf(stderr, "realloc gagal\n");
        return 1;
    }
    arr = arr_baru;          /* arr sekarang menunjuk ke blok baru */

    /* Isi 3 elemen baru */
    for (size_t i = kapasitas; i < kapasitas_baru; i++) {
        arr[i] = (int)(i * 10);
    }

    printf("\nSetelah realloc: kapasitas=%zu, alamat=%p\n",
           kapasitas_baru, (void*)arr);
    for (size_t i = 0; i < kapasitas_baru; i++) {
        printf("  arr[%zu] = %d\n", i, arr[i]);
    }

    /* Apakah realloc mengembalikan blok baru? */
    if (arr_baru != alamat_lama) {
        printf("\n(Catat: realloc mengembalikan pointer baru)\n");
    } else {
        printf("\n(Catat: realloc memperbesar di tempat)\n");
    }

    free(arr);
    return 0;
}

Output yang Dihasilkan:

text
Sebelum realloc: kapasitas=3, alamat=0x559a001e42a0
  arr[0] = 0
  arr[1] = 10
  arr[2] = 20

Setelah realloc: kapasitas=6, alamat=0x559a001e42a0
  arr[0] = 0
  arr[1] = 10
  arr[2] = 20
  arr[3] = 30
  arr[4] = 40
  arr[5] = 50

(Catat: realloc memperbesar di tempat)

Pada kasus ini realloc berhasil memperbesar di tempat—alamat lama dan alamat baru sama. Data tiga elemen awal (0, 10, 20) tetap utuh tanpa perlu disalin manual.

Mari kita telaah baris-baris kode di atas:

  1. Baris 8 (int *arr = malloc(kapasitas * sizeof(int));): alokasi awal array 3 elemen. Alamat awal dicatat ke arr.
  2. Baris 21 (int *alamat_lama = arr;): menyimpan alamat blok lama untuk dibandingkan setelah realloc.
  3. Baris 25 (int *arr_baru = realloc(arr, kapasitas_baru * sizeof(int));): meminta 6 elemen. realloc boleh mengembalikan pointer yang sama dengan arr (perbesaran in-place) atau pointer baru (blok dipindahkan). Karena itu kita tidak boleh menulis arr = realloc(arr, ...) secara langsung—bila gagal, kita kehilangan satu-satunya pointer ke blok lama.
  4. Baris 26 (if (arr_baru == NULL)): penanganan kegagalan. Bila realloc gagal, arr lama tetap valid dan harus di-free manual—tidak seperti kepercayaan populer bahwa realloc menghapus arr.
  5. Baris 32 (arr = arr_baru;): setelah realloc sukses, kita aman menimpa arr dengan pointer baru. Pointer arr lama tidak lagi valid (blok lama sudah dibebaskan oleh realloc bila dipindahkan).
  6. Baris 35-37 (arr[i] = ...): mengisi 3 elemen baru. Wilayah baru ini tidak di-zero-kan oleh realloc; bila kita tidak mengisinya, isinya tak terdefinisi.

Saat realloc Memindahkan Blok #

realloc memperbesar in-place hanya bila ada ruang bebas tepat setelah blok lama. Bila ada alokasi lain yang menghalangi, realloc harus memesan blok baru, menyalin data lama, dan membebaskan blok lama. Kita buktikan dengan mengganggu heap.

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

int main(void) {
    /* Alokasi kecil diikuti alokasi besar di tengah blok agar realloc
       harus memindahkan blok lama ke lokasi baru */
    int *a = malloc(16 * sizeof(int));
    if (!a) return 1;
    for (int i = 0; i < 16; i++) a[i] = i + 1;

    int *pengganggu = malloc(8 * sizeof(int));
    if (!pengganggu) return 1;
    for (int i = 0; i < 8; i++) pengganggu[i] = -1;

    /* Simpan alamat sebagai nilai numerik untuk dibandingkan, karena
       setelah realloc pointer lama mungkin tidak valid */
    uintptr_t alamat_lama = (uintptr_t)a;

    /* realloc dari 16 ke 1024 elemen - tidak ada ruang ekspansi, harus pindah */
    int *b = realloc(a, 1024 * sizeof(int));
    if (!b) { free(a); free(pengganggu); return 1; }
    uintptr_t alamat_baru = (uintptr_t)b;

    if (alamat_baru != alamat_lama) {
        printf("realloc memindahkan blok: lama=0x%lx, baru=0x%lx\n",
               (unsigned long)alamat_lama, (unsigned long)alamat_baru);
    } else {
        printf("realloc memperbesar di tempat: alamat=0x%lx\n",
               (unsigned long)alamat_baru);
    }

    /* Cek 16 elemen lama tetap utuh */
    printf("Data lama tetap utuh? ");
    int utuh = 1;
    for (int i = 0; i < 16; i++) {
        if (b[i] != i + 1) { utuh = 0; break; }
    }
    printf("%s\n", utuh ? "ya" : "tidak");

    free(b);
    free(pengganggu);
    return 0;
}

Output yang Dihasilkan:

text
realloc memindahkan blok: lama=0x56307d58f2a0, baru=0x56307d58f320
Data lama tetap utuh? ya

Karena alokasi pengganggu berada tepat setelah blok a di heap, realloc tidak bisa memperbesar a in-place. Alih-alih, realloc memesan blok baru berukuran 1024 elemen, menyalin 16 elemen lama, dan membebaskan blok a lama. Hasilnya: alamat baru berbeda (0x...320 bukan 0x...2a0), tetapi data 16 elemen pertama tetap utuh (1, 2, ..., 16).

Kita menggunakan uintptr_t dari <stdint.h> untuk menyimpan alamat sebagai nilai integer. Trik ini menghindari peringatan kompiler -Wuse-after-free: setelah realloc, pointer a lama bisa jadi sudah tidak valid, tetapi nilai integer alamat masih bisa dibandingkan tanpa dereference.