Setelah sebuah blok memori dibebaskan dengan free atau setelah sebuah variabel lokal keluar dari scope, pointer yang masih menyimpan alamat lama disebut dangling pointer—pointer menggantung yang menunjuk ke memori yang sudah tidak valid lagi. Dereference dangling pointer adalah undefined behavior yang sangat berbahaya karena bisa membaca data sampah, menulis ke memori yang sudah dialokasikan ulang untuk tujuan lain, atau diam-diam merusak data tanpa crash. Pada chapter ini kita akan melihat dua varian dangling pointer: setelah free dan setelah fungsi mengembalikan alamat variabel lokal—serta cara mencegahnya dengan praktek p = NULL; segera setelah free.
Dangling Setelah free #
Saat free(p) dipanggil, blok memori yang ditunjuk p dikembalikan ke heap manager dan bisa dialokasikan ulang oleh malloc berikutnya. Namun nilai p sendiri tidak diubah—p masih menyimpan alamat lama, yang sekarang sudah tidak dimiliki program. Dereference *p setelah free adalah UB: bisa membaca data lama yang kebetulan masih ada, bisa membaca data baru yang sudah ditulis pemanggilan malloc lain, atau bisa memicu crash.
Diagram berikut memperlihatkan tiga keadaan pointer p terkait siklus hidup blok heap: sebelum free, segera setelah free (dangling), dan setelah di-set ke NULL:
Pada diagram di atas, nilai pointer p tidak berubah saat free dipanggil—alamat lama 0x...2a0 tetap tersimpan di p (keadaan dangling). Yang berubah adalah kepemilikan blok heap: sebelum free, blok dimiliki program dengan isi 123; setelah free, blok dikembalikan ke heap manager dan isinya menjadi sampah yang tidak terprediksi. Praktek aman p = NULL; setelah free memutus hubungan p dengan alamat lama sehingga dereference berikutnya pasti crash (lebih mudah didiagnosis) alih-alih membaca data sampah.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = malloc(sizeof(int));
if (p == NULL) {
printf("malloc gagal\n");
return 1;
}
*p = 123;
printf("Sebelum free: p -> %p, *p = %d\n", (void*)p, *p);
free(p); /* blok dikembalikan ke heap */
/* p masih menyimpan alamat lama - ini dangling pointer */
printf("Setelah free : p -> %p (alamat masih ada, tapi blok sudah dibebaskan)\n",
(void*)p);
/* Dereference dangling pointer = UB!
*p bisa saja mengembalikan nilai lama (123), atau nilai sampah,
atau memicu crash. Tidak ada jaminan apa pun. */
printf("*p = %d (bisa apa saja - UB!)\n", *p);
/* Praktik aman: set p ke NULL segera setelah free */
p = NULL;
printf("Setelah p = NULL: p -> %p\n", (void*)p);
return 0;
}Output yang Dihasilkan:
Sebelum free: p -> 0x56308b4452a0, *p = 123
Setelah free : p -> 0x56308b4452a0 (alamat masih ada, tapi blok sudah dibebaskan)
*p = 1661514821 (bisa apa saja - UB!)
Setelah p = NULL: p -> (nil)Setelah free(p), alamat p masih 0x56308b4452a0—alamat lama. Tapi nilai *p bukan 123 lagi; sekarang 1661514821 (angka sampah yang bisa berbeda setiap eksekusi). Ini membuktikan bahwa blok memori sudah tidak dimiliki program dengan aman—isi byte bisa berubah tanpa peringatan. Kompile dengan -Wall -Wextra juga memunculkan peringatan -Wuse-after-free (GCC 12+). Praktek aman adalah mengikuti free(p); p = NULL; agar pointer NULL jika tidak sengaja dipakai lagi, dan dereference NULL paling tidak menimbulkan crash yang dapat didiagnosis.
Mari kita telaah baris-baris kode di atas:
- Baris 6-10 (
int *p = malloc(sizeof(int)); ... *p = 123;): alokasi 4 byte di heap, isi dengan123. Pemanggil harus cekNULLsebelum dereference karenamallocbisa gagal. - Baris 13 (
free(p);): kembalikan blok ke heap manager. Pointerptidak diubah—masih menyimpan alamat blok yang baru dibebaskan. - Baris 15-16 (
printf("Setelah free : p -> %p ...", (void*)p);): cetak alamat lama. Mencetak alamat (bukan dereference) tidak UB; yang UB adalah dereference*p. - Baris 21 (
printf("*p = %d", *p);): dereference dangling pointer. Compiler GCC 12+ memperingatkan-Wuse-after-free; nilai yang terbaca tidak terprediksi. - Baris 24 (
p = NULL;): praktek aman untuk mencegah dangling. Setelah baris ini,pmenjadi NULL; dereference NULL akan crash langsung (lebih mudah didiagnosis).
Dangling: Alamat Variabel Lokal yang Dikembalikan #
Varian kedua dangling pointer adalah fungsi yang mengembalikan alamat variabel lokalnya. Saat fungsi kembali, stack frame fungsi tersebut dibebaskan dan variabel lokal dianggap tidak ada lagi. Alamat yang dikembalikan menunjuk ke region stack yang mungkin sudah dipakai untuk fungsi lain—dereference akan membaca data sampah atau menimbulkan crash.
#include <stdio.h>
/* Bug klasik: mengembalikan alamat variabel lokal */
int *buat_nilai(void) {
int lokal = 42;
return &lokal; /* BUG: lokal akan mati saat fungsi kembali */
}
int main(void) {
int *p = buat_nilai();
/* p sekarang dangling - menunjuk ke stack yang sudah tidak terpakai */
printf("Nilai (UB): %d\n", *p);
return 0;
}Output yang Dihasilkan:
Segmentation fault (core dumped)Compiler GCC mendeteksi bug ini dan memunculkan peringatan function returns address of local variable [-Wreturn-local-addr]. Saat program dijalankan, dereference *p segera memicu segmentation fault dengan exit code 139. Tidak ada output printf yang sempat dicetak karena dereference terjadi sebelum printf bisa mengeluarkan apa pun. Bug seperti ini sangat umum pada pemula—selalu periksa peringatan compiler untuk -Wreturn-local-addr.