Setiap variabel dalam program C menempati ruang nyata di memori utama, dan memori utama itu sendiri hanyalah deretan sel bernomor. Topik ini membangun fondasi seluruh modul pointer, yaitu cara melihat memori sebagai sel bernomor, cara mengambil alamat sebuah variabel dengan operator &, serta cara membaca dan menulis nilai pada alamat tertentu dengan operator *. Setelah model ini terbentuk, sintaks pointer dapat dibaca sebagai kalimat yang bermakna, bukan rangkaian simbol yang harus dihafal.
RAM sebagai deretan sel bernomor#
RAM menyusun seluruh isinya dalam sel berukuran satu byte, dan setiap sel mempunyai nomor urut yang disebut alamat (address). Penomoran dimulai dari nol dan naik terus hingga batas kapasitas memori, sehingga tidak ada dua byte yang mempunyai alamat sama. Model kotak pos bernomor menggambarkan RAM dengan cukup jelas, yaitu nomor kotaknya tetap sedangkan isinya dapat diganti setiap saat. Penentuan kotak untuk setiap variabel adalah pekerjaan kompilator dan sistem operasi, sehingga program tidak pernah memilih nomor alamatnya sendiri.
Ukuran sel berbeda dengan ukuran variabel. Tipe char menempati satu sel, sedangkan tipe int biasanya menempati empat sel berurutan, dan alamat variabelnya dihitung dari sel pertama. Diagram berikut memperlihatkan dua kelompok sel tersebut beserta alamat ilustratifnya.
Operator & mengambil alamat#
Operator & yang diletakkan di depan sebuah variabel menghasilkan alamat tempat variabel itu berada. Hasilnya bukan bilangan biasa melainkan pointer, yaitu nilai yang menunjuk ke lokasi penyimpanan bertipe tertentu. Alamat itu dapat ditampung ke dalam variabel pointer yang dideklarasikan dengan tanda bintang, misalnya int *p menyatakan p bertipe pointer ke int. Program berikut mengambil alamat sebuah int, menampungnya ke dalam pointer, lalu membandingkan keduanya.
#include <stdio.h>
int main(void)
{
int nilai = 42;
int *p = &nilai;
printf("Isi nilai : %d\n", nilai);
printf("p sama dengan &nilai (1 = ya) : %d\n", p == &nilai);
return 0;
}Isi nilai : 42
p sama dengan &nilai (1 = ya) : 1Perbandingan p == &nilai menghasilkan 1 karena kedua sisi menunjuk alamat yang sama persis. Membandingkan pointer seperti ini lebih andal daripada mencetak alamatnya, karena alamat absolut berubah pada setiap eksekusi program. Dari baris deklarasinya juga terlihat kebiasaan penulisan yang lazim, yaitu tanda bintang dilekatkan pada nama variabel, sehingga int *p, *q; menyatakan dua pointer sekaligus.
Operator * membaca dan menulis nilai#
Ketika dipakai pada ekspresi, operator * membaca atau menulis isi pada alamat yang ditunjuk, sebuah operasi yang disebut dereference atau indireksi. Ekspresi *p dibaca sebagai isi dari alamat yang tersimpan dalam p, sehingga *p dapat berada di sisi kanan untuk membaca maupun di sisi kiri penugasan untuk menulis. Program berikut menggunakan satu pointer untuk membaca nilai, kemudian mengubah nilai itu tanpa menyebut nama variabelnya.
#include <stdio.h>
int main(void)
{
int stok = 25;
int *p = &stok;
printf("Isi awal lewat *p : %d\n", *p);
*p = *p - 7;
printf("Setelah *p = *p - 7 : %d\n", stok);
return 0;
}Isi awal lewat *p : 25
Setelah *p = *p - 7 : 18Penugasan *p = *p - 7 tidak menyentuh p sama sekali, melainkan sel yang ditunjuk oleh p, sehingga nilai stok ikut berubah menjadi 18. Perubahan lewat alamat inilah mekanisme yang nantinya digunakan fungsi untuk mengubah variabel pemanggilnya. Kedua operator ini juga saling berpasangan, yaitu & mengubah variabel menjadi alamat, sedangkan * mengubah alamat kembali menjadi isinya.
Jarak antarsel dan ukuran tipe#
Selisih alamat dua sel bersebelahan memperlihatkan ukuran tipe secara nyata. Pengurangan dua alamat yang telah dikonversi ke tipe char * menghasilkan jarak dalam satuan byte. Program berikut menghitung jarak antarelemen pada array char dan array int, kemudian hasilnya dapat dicocokkan dengan peta memori pada bagian pertama.
#include <stdio.h>
int main(void)
{
char huruf[4] = {'C', 'D', 'E', 'F'};
int angka[4] = {10, 20, 30, 40};
printf("Selisih byte &huruf[1] - &huruf[0]: %ld\n",
(long)((char *)&huruf[1] - (char *)&huruf[0]));
printf("Selisih byte &huruf[3] - &huruf[0]: %ld\n",
(long)((char *)&huruf[3] - (char *)&huruf[0]));
printf("Selisih byte &angka[1] - &angka[0]: %ld\n",
(long)((char *)&angka[1] - (char *)&angka[0]));
printf("Selisih byte &angka[3] - &angka[0]: %ld\n",
(long)((char *)&angka[3] - (char *)&angka[0]));
return 0;
}Selisih byte &huruf[1] - &huruf[0]: 1
Selisih byte &huruf[3] - &huruf[0]: 3
Selisih byte &angka[1] - &angka[0]: 4
Selisih byte &angka[3] - &angka[0]: 12Elemen char berjarak satu byte karena satu char memang menempati satu sel. Elemen int berjarak empat byte, yang berarti satu int menempati empat sel berurutan. Angka empat itu sesuai dengan sizeof(int) pada mesin yang menjalankan contoh, sehingga ukuran tipe dapat diverifikasi sendiri dari peta memori. Karena angkanya sama, operator sizeof menjadi alat pendamping yang wajar untuk memeriksa ukuran tipe.
Perlu ditegaskan bahwa alamat hanya berperan sebagai penunjuk tempat, bukan sebagai bilangan yang boleh dihitung seenaknya. Program sehari-hari hampir tidak pernah menuliskan nomor alamat secara manual, karena pekerjaan itu milik kompilator dan sistem operasi. Yang menjadi milik penulis program adalah keputusan menyimpan alamat ke dalam pointer, mengubah isi lewat dereference, dan menjaga agar pointer tidak menunjuk sel yang tidak dimilikinya. Jarak ini pula yang menjelaskan mengapa p + 1 pada aritmetika pointer tidak selalu berpindah satu byte.
Kesalahan umum#
- Mencetak alamat dengan penentu
%d. Alamat bukanint, sehinggaprintf("%d", &nilai)menghasilkan keluaran yang tidak bermakna dan memunculkan peringatan kompilator. Gunakan penentu%puntuk inspeksi cepat, atau selisih alamat seperti pada contoh agar keluarannya stabil. - Mengira tanda bintang pada deklarasi berbeda makna dengan tanda bintang pada ekspresi. Pada
int *p, tanda bintang menyatakanpbertipe pointer keint, sedangkan pada*p = 5tanda yang sama merupakan operator dereference. Keduanya terbaca berbeda karena konteksnya berbeda, tetapi berakar pada satu gagasan yang sama. - Mengerjakan
*psebelumpberisi alamat yang sah. Pointer yang belum diinisialisasi menunjuk alamat sembarang, dan penulisan nilai ke alamat itu dapat menghentikan program tanpa pesan yang jelas. Inisialisasi pointer segera setelah dideklarasikan, misalnya dengan NULL apabila alamat yang sah belum tersedia.
Ringkasan#
- Memori adalah deretan sel satu byte bernomor, dan alamat sebuah variabel adalah nomor sel pertama yang menampungnya.
- Operator
&menghasilkan alamat variabel, sedangkan operator*membaca atau menulis isi pada alamat yang ditunjuk. - Selisih alamat dua elemen array
intberjarak empat byte, sesuai ukuran tipe yang dilaporkansizeof.
Referensi#
- Pointer declaration — cppreference.com — Kaidah deklarasi pointer serta hubungan tipe pointer dengan tipe objek yang ditunjuknya.
- Member access operators — cppreference.com
— Spesifikasi operator alamat
&dan operator dereference*pada bahasa C.