Variabel yang menyimpan alamat
Setiap variabel tinggal di suatu tempat di memori, di lokasi bernomor yang disebut alamat. Biasanya kamu tidak peduli di mana letaknya, kamu cukup memakai nama variabelnya. Pointer membalik hal itu: pointer adalah variabel yang nilainya berupa alamat. Alih-alih menyimpan 42, ia menyimpan "tempat 42 disimpan".
Perantara inilah yang membuat pointer begitu kuat. Fungsi bisa mengubah variabel milik pemanggilnya lewat pointer, struktur data seperti linked list menyambungkan node-node dengan pointer, dan (seperti yang akan kamu lihat di bagian memori dinamis) pointer adalah cara kamu menjangkau memori yang dialokasikan saat runtime.
& pada &score adalah operator address-of: operator ini menghasilkan lokasi dari score. * pada *p adalah operator dereference: operator ini mengikuti alamat itu kembali ke nilai yang tinggal di sana.
Dua operator: & dan *
Hal yang paling membingungkan bagi pemula adalah * punya dua arti tergantung letaknya. Pahami perbedaannya:
int* p; // DECLARATION: "p is a pointer to int"
p = &x; // & = address-of: store the address of x in p
int y = *p; // * = dereference: read the value p points at
*p = 99; // dereference on the left: write through the pointer
Dalam deklarasi, * adalah bagian dari tipe. Dalam ekspresi, * melakukan sesuatu. Begitu sebuah pointer sudah disiapkan, dereference memberimu akses baca/tulis penuh ke variabel aslinya:
Perhatikan bahwa kamu tidak pernah menyentuh health lewat namanya setelah baris 1, tapi nilainya terus berubah. Itulah intinya: hp adalah alias untuk tempat penyimpanan yang sama. Penulisan spasi (int* p, int *p, int*p) hanya soal tampilan dan sama saja bagi compiler. Panduan ini memakai int* p.
nullptr: tidak menunjuk ke apa pun
Pointer yang tidak menunjuk ke mana pun sebaiknya diisi nullptr (C++11). Ini cara yang jelas dan aman tipe untuk mengatakan "belum ada target", dan memberimu sesuatu untuk diuji sebelum melakukan dereference.
Pilih nullptr daripada makro lama NULL atau 0 biasa. Karena nullptr punya tipe pointer sungguhan, ia tidak pernah salah dibaca sebagai integer 0 saat overload resolution, bug halus yang bisa muncul dengan gaya lama.
Jebakan: dereference null. Membaca atau menulis lewat pointer null (atau pointer yang belum diinisialisasi) adalah undefined behavior, dan biasanya langsung membuat program crash:
int* p = nullptr;
cout << *p; // CRASH - dereferencing null is undefined behavior
Selalu lindungi dengan if (p) (atau if (p != nullptr)) sebelum melakukan dereference pada apa pun yang mungkin null.
Pointer dan array
Nama sebuah array berubah (decay) menjadi pointer ke elemen pertamanya, jadi pointer dan array sangat erat hubungannya. Menambahkan 1 ke pointer tidak menambah satu byte, tapi maju satu elemen. Itulah yang membuat aritmatika pointer bekerja:
p[i] dan *(p + i) benar-benar ekspresi yang sama, dan kesamaan itulah alasan indeks array dimulai dari nol. Bug klasik di sini adalah berjalan melewati ujung: nums + 4 adalah penanda satu-lewat-akhir yang sah untuk dibandingkan, tapi dereference *(nums + 4) membaca di luar batas. Kesalahan off-by-one dengan pointer adalah penyebab utama crash dan kerusakan data yang diam-diam, jadi tentukan kondisi berhentimu dengan sengaja.
const dan pointer
const bisa berlaku untuk apa yang ditunjuk pointer, untuk pointer itu sendiri, atau keduanya. Baca deklarasinya dari kanan ke kiri untuk memahaminya:
const int* p; // pointer to const int - can't change *p, can repoint p
int* const p = &x; // const pointer to int - can change *p, can't repoint p
const int* const p = &x; // both locked
Hal ini selalu penting di kode sungguhan. Fungsi yang berjanji tidak akan mengubah datamu menerima pointer ke const:
Menandai nilai yang ditunjuk sebagai const mendokumentasikan maksudmu dan membuat compiler bisa mencegah penulisan yang tidak disengaja. Keamanan gratis tanpa biaya runtime sama sekali.
Jebakan besar: dangling pointer
Dangling pointer menunjuk ke memori yang tidak lagi menyimpan nilai yang kamu harapkan: variabelnya sudah keluar dari scope, atau memorinya sudah dibebaskan. Melakukan dereference padanya adalah undefined behavior, dan bagian buruknya, sering kali kelihatannya berjalan sampai tiba-tiba tidak.
int* makeBad() {
int local = 5;
return &local; // BUG: local dies when the function returns
} // the returned pointer now dangles
Alamatnya masih berupa angka yang sah, tapi ia menunjuk ke slot stack yang sudah diambil kembali. Membacanya menghasilkan nilai sampah atau crash. Hal yang sama terjadi kalau kamu menyimpan pointer ke objek heap yang sudah di-delete atau ke elemen vector yang kemudian dialokasikan ulang.
Tiga aturan ini menjaga kamu tetap aman:
- Jangan pernah mengembalikan alamat dari variabel lokal. Kembalikan berdasarkan nilai, atau biarkan pemanggil yang memiliki tempat penyimpanannya.
- Isi pointer dengan
nullptrsetelah hal yang ditunjuknya sudah hilang, dan cek sebelum dipakai. - Untuk urusan kepemilikan dan masa hidup objek, pakai smart pointer alih-alih
new/deletementah. Smart pointer membebaskan memori secara otomatis dan mengurangi seluruh jenis bug ini.
Berikutnya: reference vs pointer
Pointer bukan satu-satunya cara merujuk variabel lain secara tidak langsung. C++ juga punya reference, yang terasa mirip tapi tidak bisa null, tidak bisa dipindahkan ke target lain, dan sintaksnya lebih bersih. Berikutnya kita akan membandingkan keduanya di halaman reference vs pointer, supaya kamu tahu persis alat mana yang harus dipakai, dan kenapa C++ modern lebih memilih reference kalau memungkinkan.
Pertanyaan yang sering diajukan
Apa itu pointer di C++?
Pointer adalah variabel yang menyimpan alamat memori dari nilai lain, bukan nilai itu sendiri. Kamu mendeklarasikannya dengan * (misalnya int* p), mengambil alamat dengan operator & (p = &x), lalu membaca atau menulis nilai yang ditunjuknya dengan dereference memakai *p.
Apa perbedaan & dan * pada pointer C++?
Dalam konteks pointer, & adalah operator address-of: &x memberimu alamat dari x. * punya dua tugas: dalam deklarasi (int* p) ia menandai variabel sebagai pointer, dan dalam ekspresi (*p) ia melakukan dereference pada pointer untuk mencapai nilai yang tersimpan di alamat itu.
Apa itu nullptr di C++ dan kenapa dipakai sebagai pengganti NULL?
nullptr adalah literal null pointer yang aman tipe (type-safe), ditambahkan di C++11. Artinya "tidak menunjuk ke apa pun". Pilih ini daripada NULL atau 0 yang lama, karena nullptr punya tipe pointer sungguhan, sehingga tidak pernah dikira integer saat overload resolution. Selalu cek if (p) sebelum dereference, karena dereference null pointer adalah undefined behavior.