Deklarasi pointer hanya menambahkan satu tanda bintang pada deklarasi variabel biasa, tetapi keadaan isinya menentukan apakah program berjalan aman atau berhenti mendadak. Topik ini membahas sintaks deklarasi pointer untuk berbagai tipe, aturan inisialisasinya, serta tiga keadaan isi yang perlu dibedakan sejak awal, yaitu pointer yang belum diinisialisasi, pointer yang berisi NULL, dan pointer liar. Pembedaan ketiganya menjadi bekal untuk membaca pesan error dan mendiagnosis bug pointer.
Membaca deklarasi pointer#
Deklarasi int *p memuat dua informasi sekaligus, yaitu tipe dasar int dan tanda bintang yang menyatakan p sebagai pointer ke tipe itu. Tipe dasar bukan hiasan, karena kompilator menggunakannya untuk menentukan berapa byte yang dibaca saat dereference dan sejauh mana pointer bergeser saat ditambah bilangan. Deklarasi untuk tipe lain mengikuti pola yang sama, misalnya char *pc, double *pd, dan int **pp untuk pointer ke pointer.
Tipe yang ditunjuk tidak memengaruhi ukuran variabel pointernya sendiri. Pointer hanya menyimpan satu alamat, dan pada komputer 64-bit satu alamat ditampung dalam delapan byte, apa pun tipe yang ditunjuknya. Program berikut membuktikannya dengan tiga pointer bertipe berbeda.
#include <stdio.h>
int main(void)
{
char *pc = 0;
int *pi = 0;
double *pd = 0;
printf("sizeof(char *) : %zu\n", sizeof(pc));
printf("sizeof(int *) : %zu\n", sizeof(pi));
printf("sizeof(double *) : %zu\n", sizeof(pd));
return 0;
}sizeof(char *) : 8
sizeof(int *) : 8
sizeof(double *) : 8Ketiga angka sama besar karena ketiga variabel sama-sama menyimpan alamat pada mesin 64-bit. Ukuran tipe yang ditunjuk tetap berperan pada saat dereference, yaitu *pc membaca satu byte sedangkan *pd membaca delapan byte. Pada arsitektur 16-bit atau 32-bit angka pada keluaran dapat berbeda, tetapi kesimpulannya tetap, yaitu ukuran pointer ditentukan oleh arsitektur, bukan oleh tipe yang ditunjuk.
Satu konvensi lagi membuat deklarasi pointer lebih mudah diaudit, yaitu menginisialisasi pointer pada baris yang sama dengan deklarasinya. Bentuk int *p = &nilai; menuntaskan dua pekerjaan sekaligus, sehingga tidak ada jeda ketika pointer berada dalam keadaan tidak tentu. Apabila alamat yang sah memang belum tersedia, bentuk int *p = NULL; menjadi pilihan yang disarankan karena keadaannya dapat diperiksa. Kedua kebiasaan ini terlihat sepele, tetapi keduanya mencegah kelas bug pointer yang paling sulit dilacak.
Tiga keadaan isi pointer#
Pointer yang dideklarasikan tanpa inisialisasi berada pada keadaan yang paling berbahaya, yaitu isinya tidak tentu. Isinya bisa berupa alamat sembarang milik data lama. Menggunakan pointer seperti itu termasuk undefined behavior, yakni perilaku yang tidak ditetapkan standar C, sehingga hasilnya dapat berbeda antarkompilator maupun antareksekusi. Keadaan ini diperbaiki dengan memberi nilai segera setelah deklarasi.
Keadaan kedua adalah NULL, yaitu nilai konstanta pointer yang menyatakan bahwa pointer sengaja tidak menunjuk ke mana pun. NULL tersedia melalui header <stddef.h> dan juga ikut terbawa oleh header standar lain seperti <stdio.h>. Pointer yang berisi NULL boleh dibandingkan dan boleh diperiksa, tetapi tetap tidak boleh didereference.
Keadaan ketiga adalah pointer liar, yaitu pointer yang menunjuk alamat sembarang akibat kesalahan logika, misalnya alamat variabel lokal yang masa hidupnya sudah berakhir. Pointer liar sering tidak menimbulkan error seketika, sehingga justru paling sulit dilacak.
Deklarasi tanpa inisialisasi pada potongan berikut hanya boleh dibaca, bukan ditiru. Baris *p = 5; yang sengaja dikomentari akan menulis angka 5 ke alamat sembarang, dan alamat itu bisa saja milik data program lain.
// tidak-dijalankan
int *p; /* isi tidak tentu, menunjuk alamat sembarang */
int nilai = 5;
/* *p = nilai; berbahaya: menulis ke alamat yang tidak sah */
/* p = &nilai; perbaikan: isi alamat yang sah sebelum dereference */Memeriksa NULL sebelum dereference#
NULL sering berperan sebagai tanda kegagalan yang sah. Fungsi yang mengembalikan pointer dapat menggunakan NULL untuk menyatakan bahwa hasilnya tidak ada, sedangkan pemanggil wajib memeriksa nilainya sebelum melakukan dereference. Pola cari lalu periksa ini juga digunakan pustaka standar, misalnya fopen yang mengembalikan NULL ketika berkas tidak dapat dibuka. Program berikut mencari sebuah nilai dalam array, lalu menangani kedua kemungkinan hasilnya.
#include <stdio.h>
#include <stddef.h>
/* Mengembalikan alamat elemen pertama yang bernilai target,
atau NULL bila tidak ditemukan. */
int *cari(int *data, int n, int target)
{
for (int i = 0; i < n; i++) {
if (data[i] == target) {
return &data[i];
}
}
return NULL;
}
int main(void)
{
int stok[] = {4, 8, 15, 16, 23};
int *hasil = cari(stok, 5, 15);
if (hasil != NULL) {
printf("Nilai 15 ditemukan pada indeks %ld\n", (long)(hasil - stok));
}
int *kosong = cari(stok, 5, 99);
if (kosong == NULL) {
printf("Nilai 99 tidak ditemukan, pointer bernilai NULL\n");
}
return 0;
}Nilai 15 ditemukan pada indeks 2
Nilai 99 tidak ditemukan, pointer bernilai NULLFungsi cari mengembalikan alamat elemen yang cocok atau NULL apabila tidak ada yang cocok. Pemanggil memeriksa pointer terhadap NULL sebelum menggunakannya, sehingga kedua jalur sama-sama aman. Bentuk if (hasil != NULL) juga lazim ditulis singkat menjadi if (hasil), karena pointer non-null dinilai benar dalam konteks kondisi.
Satu catatan lagi melengkapi pola ini, yaitu NULL boleh dibandingkan dengan bilangan nol dalam ekspresi seperti p == 0, karena konstanta nol dalam konteks pointer dinilai sebagai pointer nol. Meskipun sah, penulisan NULL dipilih pada contoh karena maksudnya terbaca sebagai keadaan pointer, bukan sebagai bilangan. Kebiasaan yang sama berlaku pada kondisi perulangan, sehingga seluruh kode dalam modul ini konsisten menggunakan bentuk eksplisit tersebut.
Kesalahan umum#
- Menulis
int* a, b;dengan anggapan keduanya pointer. Tanda bintang melekat pada deklarator, sehingga hanyaayang bertipeint *, sedangkanbbertipeintbiasa. Deklarasikan satu pointer per baris agar maksudnya tidak rancu. - Menganggap pointer yang belum diinisialisasi berisi NULL. Isinya tidak tentu, sehingga pemeriksaan
p == NULLtidak dapat dijadikan penanda keadaan awal. Beri nilaiNULLpada saat deklarasi bila pointer memang belum menunjuk ke mana pun. - Menderereference NULL tanpa pemeriksaan. Ekspresi
*ppadapbernilai NULL menghentikan program dengan error segmentasi. Periksa pointer terlebih dahulu, dan topik Bug Pointer membahas cara membaca pesan crash tersebut.
Ringkasan#
int *pmenyatakan pointer keint, dan ukuran pointer ditentukan oleh arsitektur, yaitu delapan byte pada mesin 64-bit, bukan oleh tipe yang ditunjuk.- Pointer yang belum diinisialisasi berisi nilai tidak tentu dan tidak boleh digunakan, sedangkan NULL menyatakan secara jelas bahwa pointer tidak menunjuk ke mana pun.
- Pointer liar menunjuk alamat sembarang tanpa menimbulkan error seketika, sehingga inisialisasi awal dan pemeriksaan NULL menjadi kebiasaan yang wajib.
Referensi#
- NULL pointer — cppreference.com — Definisi konstanta pointer nol dan aturan pemakaiannya.
- Pointer declaration — cppreference.com — Sintaks deklarasi pointer, termasuk pointer bertingkat dan inisialisasinya.