Setiap variabel di programmu tinggal di suatu tempat di memori, dan setiap "tempat" punya nomor, yaitu alamatnya. Pointer hanyalah variabel yang menyimpan salah satu nomor itu. Itulah seluruh idenya. Semua hal lain di halaman ini adalah sintaks dan akibatnya.
Pointer begitu penting di C karena C memberi setiap fungsi salinan argumennya. Fungsi yang diberi angka 7 boleh mengubah salinannya sesuka hati; variabel milik pemanggil tidak pernah bergeser. Berikan alamatnya, maka fungsi itu bisa menjangkau balik dan mengubah aslinya. Array, string, memori dinamis, dan setiap struktur data yang akan kamu bangun di C bertumpu pada satu kemampuan itu.
Memori, alamat, dan kenapa semuanya punya alamat
Saat kamu menulis int score = 42;, compiler mencadangkan beberapa byte memori dan memberinya nama yang bisa kamu pakai. Byte-byte itu juga punya alamat berupa angka. Bayangkan memori sebagai jalan yang sangat panjang dengan rumah-rumah bernomor: score adalah keluarga yang tinggal di rumah nomor 0x7ffd4c2a. Namanya untukmu, nomornya untuk mesin.
Operator & meminta nomor itu:
Jalankan. Ketiga alamatnya berbeda karena ketiga variabel menempati byte yang berbeda, dan alamatnya akan berubah setiap kali dijalankan. Itu normal, dan karena itulah kamu tidak pernah menulis alamat secara langsung di kode. Perhatikan cast (void *): %p ditetapkan untuk menerima void *, dan memberikan tipe pointer lain secara teknis adalah undefined behavior. Jadikan cast itu kebiasaan.
Alamat yang tidak bisa ditebak bukan masalah, karena kamu hampir tidak pernah peduli alamatnya berapa. Yang kamu pedulikan adalah kamu memilikinya.
Mendeklarasikan pointer
Deklarasi pointer menyebutkan tipe yang ditunjuknya:
int *p; // p can hold the address of an int
double *q; // q can hold the address of a double
char *name; // name can hold the address of a char
Baca int *p; sebagai "*p adalah int": melakukan dereference pada p memberimu sebuah int. Cara membaca ini tetap berlaku untuk setiap deklarasi rumit yang nanti dilemparkan C kepadamu.
Letak * adalah pilihan gaya yang diabaikan compiler: int* p, int *p, dan int * p semuanya mendeklarasikan hal yang sama. Kebanyakan kode C menulis int *p, dan ada alasan praktisnya:
int* a, b; // a is a pointer to int; b is a PLAIN int, not a pointer
int *a, *b; // both are pointers - the * binds to the name, not the type
* menempel ke deklarator, bukan ke tipenya. Menulisnya di samping nama membuat hal itu tetap terlihat.
& dan *: dua operatornya
Dua operator melakukan semua pekerjaan, dan keduanya persis berlawanan.
&x: address-of. Menghasilkan alamat tempatxberada.*p: dereference. Pergi ke alamat dipdan memberimu objek yang tinggal di sana.
Dua baris terakhir itulah inti dari pointer. Tidak ada yang memberi nilai ke age secara langsung, tapi age berubah, karena *p = 31 berarti "simpan 31 di alamat yang dipegang p", dan alamat itu adalah alamat age.
Begini gambarannya di memori:
p age
+------------+ +----------+
| 0x7ffd1c40 | --------> | 31 |
+------------+ +----------+
at 0x7ffd1c38 at 0x7ffd1c40
p -> the address 0x7ffd1c40
*p -> the int stored there, 31
&age -> 0x7ffd1c40, the same address p holds
&p -> 0x7ffd1c38, where p itself lives
Perhatikan baris terakhir: pointer sendiri juga variabel yang ada di suatu tempat di memori, jadi ia juga punya alamatnya sendiri. Itu bukan pertanyaan jebakan, hanya konsistensi, dan begitulah pointer ke pointer (int **) bekerja.
Karena & dan * saling membatalkan, *&age sama saja dengan age, dan &*p sama saja dengan p.
Kenapa tipe pointer penting
Pointer menyimpan alamat, dan alamat hanyalah angka, jadi kenapa compiler bersikeras ingin tahu apakah pointer itu menunjuk ke int atau double? Ada dua alasan:
- Berapa byte yang harus dibaca. Dereference
int *membaca 4 byte (biasanya); dereferencedouble *membaca 8. Tanpa tipe,*ptidak punya arti. - Bagaimana menafsirkan byte-byte itu. Pola bit yang sama adalah satu angka sebagai
intdan angka yang sama sekali berbeda sebagaifloat.
Sebaliknya, ukuran pointer itu sendiri tidak bergantung pada apa yang ditunjuknya. Alamat ya alamat:
Di mesin 64-bit, ketiga pointer berukuran 8 byte, sementara hal yang ditunjuknya berukuran 4, 8, dan 1. Tipe yang ditunjuklah yang membuat *p dan aritmetika pointer punya arti.
Memberi nilai antartipe pointer adalah error yang akan dikeluhkan compiler, dan alasannya kuat: pi = &d; akan membuatmu membaca 4 byte dari sebuah double lalu menyebut hasilnya integer.
Motivasi klasik: swap()
Inilah fungsi yang selalu dipakai setiap kursus C, karena gagal dengan cara yang persis menjelaskan pointer.
Fungsinya melaporkan bahwa ia sudah menukar keduanya, dan main melaporkan bahwa tidak ada yang terjadi. broken_swap menerima salinan x dan y. Fungsi itu menukar salinannya dengan sempurna, lalu salinan itu lenyap saat fungsinya selesai.
Berikan alamatnya sebagai gantinya:
Sekarang a dan b tetap berupa salinan, salinan dari dua alamat, tapi salinan sebuah alamat menunjuk ke variabel yang sama persis dengan aslinya. *a adalah x, dari mana pun fungsinya dipanggil.
Inilah pola di balik setiap fungsi C yang mengubah data milik pemanggilnya, dan di balik parameter fungsi pada umumnya: C tidak punya pass-by-reference, jadi kamu mengirim pointer lalu melakukan dereference padanya. Itu juga alasan scanf butuh &:
int n;
scanf("%d", &n); // scanf must be able to write into n, so it needs n's address
Mengembalikan lebih dari satu nilai
Fungsi C mengembalikan satu nilai. Pointer adalah cara untuk mengakalinya: kirim alamat untuk hasil-hasil tambahannya.
Nilai kembalian membawa status, dan pointer membawa hasilnya. Pembagian seperti itu ada di mana-mana di library standar C sendiri dan di API sistem operasi.
Pointer ke pointer
Karena pointer adalah variabel, kamu bisa mengambil alamatnya, dan tipe alamat itu adalah "pointer ke pointer ke int":
pp p value
+------+ +------+ +------+
| &p | ----> | &val | -----> | 8 |
+------+ +------+ +------+
Kamu akan benar-benar bertemu ** saat sebuah fungsi perlu mengubah pointer yang dipegang pemanggil, misalnya alokator yang mengarahkan pointer pemanggil ke memori baru, dan di char *argv[] pada argumen baris perintah.
Empat kesalahan yang perlu kamu tahu sebelum melakukannya
Memakai pointer yang tidak pernah diarahkan ke mana pun. Pointer yang belum diinisialisasi menyimpan byte apa saja yang sudah ada di sana. Melakukan dereference padanya membaca atau menulis alamat acak.
int *p; // p holds garbage
*p = 10; // undefined behavior - likely a crash
Inisialisasi setiap pointer, dengan alamat sungguhan atau dengan NULL. Lihat halaman pointer NULL untuk disiplin seputar hal itu.
Tertukar antara *p dan p. p = 5; mengarahkan pointer ke alamat 5 (tidak masuk akal); *p = 5; menyimpan 5 di tempat yang ditunjuknya. Compiler akan memberi peringatan untuk yang pertama, dan peringatan itu layak dibaca.
Lupa & di scanf. scanf("%d", n) mengirim nilai n sebagai alamat. Kode itu bisa dikompilasi dengan peringatan, lalu menulis ke memori mana pun yang ditunjuk angka itu.
Mengembalikan alamat variabel lokal. Variabel lokal itu lenyap begitu fungsinya selesai; alamatnya hanyalah kuitansi untuk bangunan yang sudah dirobohkan.
int *bad(void) {
int local = 42;
return &local; // the caller gets a dangling pointer
}
Kalau kamu butuh memori yang bertahan lebih lama dari pemanggilan, alokasikan memori. Untuk itulah memori dinamis ada.
Ke mana selanjutnya
Pointer tidak berhenti di "menyimpan satu alamat". Menambahkan angka ke pointer membuatnya melangkah di memori dalam satuan tipenya, dan itu topik aritmetika pointer; lalu nama array di C berubah (decay) menjadi pointer ke elemen pertamanya, dan karena itulah pointer dan array pada dasarnya satu topik begitu kamu memahami keduanya. Baca dua halaman itu berikutnya, dan sisa C tidak akan lagi terlihat seperti sihir.
Pertanyaan yang sering diajukan
Apa itu pointer di C?
Pointer adalah variabel yang nilainya berupa alamat memori, yaitu lokasi objek lain di memori. int *p = &age; membuat p menyimpan alamat age, lalu *p membaca atau menulis variabel age lewat alamat itu.
Apa bedanya * dan & di C?
&x adalah operator address-of: menghasilkan alamat tempat x berada. *p adalah operator dereference: pergi ke alamat yang disimpan di p dan memberimu objek yang ada di sana. Keduanya saling membatalkan, jadi *&x sama saja dengan x.
Kenapa * muncul dua kali, di deklarasi dan saat memakai pointer?
Itu dua pemakaian berbeda dari simbol yang sama. Di int *p;, * adalah bagian dari tipe, yang berarti "p adalah pointer ke int". Di *p = 5;, * adalah operator dereference, yang berarti "simpan 5 di alamat yang dipegang p". Membaca deklarasinya sebagai "*p adalah int" membuat keduanya masuk akal.
Kenapa butuh pointer di C?
C mengirim setiap argumen berdasarkan nilai, jadi sebuah fungsi tidak akan pernah bisa mengubah variabel milik pemanggil kecuali diberi alamatnya. Pointer juga memungkinkanmu menelusuri array dengan efisien, mengembalikan lebih dari satu hasil, membangun struktur berantai, dan memakai memori yang dialokasikan saat runtime dengan malloc.