Anggota struct dapat diakses langsung melalui variabelnya, tetapi pointer struct mengaksesnya melalui alamat. Bentuk akses ini muncul di hampir seluruh kode C praktis, misalnya saat struct besar dioper ke fungsi agar tidak disalin atau saat data saling terhubung satu sama lain. Topik ini membahas pendeklarasian pointer struct, perbandingan dua notasi akses anggota, yaitu (*p).field dan p->field, serta pemakaiannya sebagai parameter fungsi.
Menyimpan alamat struct#
Pointer bertipe struct dideklarasikan dengan menambahkan tanda * pada nama variabelnya, lalu diinisialisasi dengan alamat variabel struct yang diperoleh dari operator &. Tipenya harus cocok, sehingga struct Siswa * hanya menyimpan alamat variabel struct Siswa dan tidak untuk tipe lain. Pointer menempati ruang sebesar satu alamat, yaitu 8 byte pada Linux x86-64, berapa pun besarnya struct yang ditunjuk.
Operator & memang diperlukan di sini karena nama struct tidak meluruh menjadi pointer sebagaimana nama array. Ekspresi &s menghasilkan alamat awal variabel s, dan tipe hasilnya persis struct Siswa *. Setelah pointer itu terisi, seluruh anggota variabel aslinya dapat dicapai melalui pointer tersebut tanpa menyalin apa pun.
// tidak-dijalankan
struct Siswa s = {"Citra", 78};
struct Siswa *p = &s; /* p menyimpan alamat variabel s */Mengakses anggota melalui dereferensi#
Karena p hanya menyimpan alamat, anggotanya dibaca setelah pointer didereferensi terlebih dahulu. Ekspresi (*p).nilai mendereferensi p menjadi variabel s, kemudian operator titik membaca anggota nilai. Tanda kurung wajib ditulis karena operator titik lebih kuat daripada operator dereferensi, sehingga tanpa tanda kurung ekspresi dibaca kompiler dalam urutan yang berbeda.
// tidak-dijalankan
*p.nilai = 85; /* dibaca kompiler sebagai *(p.nilai) dan error saat kompilasi */Program berikut mengubah anggota nilai melalui pointer dengan kedua notasi, dimulai dari bentuk dereferensi penuh.
#include <stdio.h>
struct Siswa {
char nama[16];
int nilai;
};
int main(void) {
struct Siswa s = {"Citra", 78};
struct Siswa *p = &s;
(*p).nilai = 85;
printf("Setelah (*p).nilai = 85 : %s bernilai %d\n", s.nama, s.nilai);
p->nilai = 90;
printf("Setelah p->nilai = 90 : %s bernilai %d\n", s.nama, s.nilai);
return 0;
}Setelah (*p).nilai = 85 : Citra bernilai 85
Setelah p->nilai = 90 : Citra bernilai 90Kedua penugasan mengubah anggota nilai pada variabel s yang sama, dan bukan pada salinannya. Bentuk (*p).nilai memperlihatkan mekanisme dereferensinya secara eksplisit, sedangkan perubahan berikutnya melalui p->nilai juga terbaca pada keluaran. Diagram berikut merangkum kedua notasi yang menunjuk anggota yang identik.
Operator panah ->#
Operator panah (->) menggabungkan dereferensi dan akses anggota dalam satu lambang. Ekspresi p->nilai memiliki makna yang persis sama dengan (*p).nilai, tetapi lebih pendek dan tidak memerlukan tanda kurung. Karena itulah operator panah menjadi notasi baku pada kode C untuk membaca dan menulis anggota melalui pointer, sedangkan bentuk dereferensi penuh biasanya hanya muncul saat menjelaskan mekanismenya.
Pointer struct paling sering dipakai sebagai parameter fungsi. Fungsi berikut menerima alamat struct, sehingga perubahan yang dilakukannya langsung terjadi pada variabel pemanggil. Pola ini mengatasi keterbatasan salinan pada pemanggilan fungsi biasa, sebab fungsi menerima alamat variabel aslinya.
#include <stdio.h>
struct Siswa {
char nama[16];
int nilai;
};
void naikkan(struct Siswa *s, int tambahan) {
s->nilai = s->nilai + tambahan;
}
int main(void) {
struct Siswa daftar[3] = {
{"Ali", 70},
{"Budi", 82},
{"Citra", 78}
};
int i;
for (i = 0; i < 3; i = i + 1) {
naikkan(&daftar[i], 5);
printf("%s : %d\n", daftar[i].nama, daftar[i].nilai);
}
return 0;
}Ali : 75
Budi : 87
Citra : 83Fungsi naikkan menerima alamat satu elemen array melalui &daftar[i] dan menaikkan anggota nilai menggunakan operator panah. Keluaran menunjukkan nilai ketiga siswa naik lima poin karena fungsi mengubah struct aslinya dan bukan salinannya. Inilah alasan pointer struct juga dipilih ketika fungsi memang perlu mengubah data milik pemanggil.
Anggota berupa array diperlakukan sebagaimana array biasa pada saat diakses melalui pointer. Ekspresi s->nama menghasilkan array nama milik variabel yang ditunjuk, sehingga mengisinya memerlukan fungsi seperti strcpy dari header string.h dan bukan operator penugasan. Pembacaan per karakter maupun pencetakan dengan %s berjalan sebagaimana biasa, misalnya printf("%s\n", s->nama);.
Pointer struct juga mengikuti aturan aritmetika pointer yang sama dengan pointer lain. Kenaikan satu satuan menggeser pointer sebesar ukuran satu elemen, sehingga p + 1 melompati satu struct utuh dan bukan satu byte. Ukuran lompatannya dapat dicek dengan sizeof(struct Siswa), dan sifat inilah yang membuat penelusuran array struct dengan pointer tetap mendarat tepat pada awal setiap elemen.
// tidak-dijalankan
struct Siswa *q = daftar; /* menunjuk elemen pertama */
printf("%s\n", q->nama); /* elemen indeks 0 */
q = q + 1; /* melompat sebesar sizeof struct */
printf("%s\n", q->nama); /* elemen indeks 1 */Kesalahan umum#
Menggunakan operator titik pada pointer merupakan kekeliruan yang paling sering. Ekspresi p.nilai ditolak kompiler dengan pesan error ‘p’ is a pointer; did you mean to use ‘->’? karena anggota nilai tidak pernah menjadi bagian dari pointer. Kejadian sebaliknya, yaitu operator panah pada variabel biasa, menghasilkan pesan invalid type argument of ‘->’. Kedua pesan menyebutkan dugaan perbaikannya, sehingga membacanya dengan teliti mempercepat penyuntingan.
Kekeliruan kedua adalah mendereferensi pointer yang belum menunjuk variabel mana pun, misalnya pointer yang baru dideklarasikan atau yang bernilai NULL. Programnya tetap berhasil dikompilasi, tetapi berhenti mendadak pada saat berjalan. Inisialisasi pointer dengan alamat yang sah segera setelah deklarasinya, dan periksa NULL terlebih dahulu sebelum mendereferensi pointer yang bisa saja kosong.
Kekeliruan ketiga adalah menyimpan alamat struct yang sudah tidak berlaku, misalnya alamat variabel lokal milik fungsi yang telah berakhir. Pointer tersebut tetap menyimpan angka alamatnya, tetapi memorinya tidak lagi menjamin isinya, dan pembacaannya menghasilkan nilai yang tak terduga. Pastikan variabel yang ditunjuk masih hidup selama pointer dipakai, misalnya dengan menyimpan struct pada lingkup yang lebih lama atau mengalokasikannya secara dinamis.
Ringkasan#
- Pointer struct menyimpan alamat variabel struct, dideklarasikan dengan tanda
*, dan diinisialisasi dengan operator&. (*p).fieldmendereferensi pointer terlebih dahulu, dan tanda kurungnya wajib karena operator titik lebih kuat daripada operator dereferensi.p->fieldmerupakan padanan persis dari(*p).fielddan menjadi notasi baku pada kode C.- Aritmetika pointer pada struct melompat sebesar ukuran satu struct, sehingga penelusuran array dengan pointer tetap mendarat pada tiap elemen.
- Parameter berupa pointer struct memungkinkan fungsi membaca dan mengubah struct milik pemanggil tanpa menyalinnya.
Referensi#
- Member access operators — Cppreference — Kaidah operator titik dan panah beserta urutan pengerjaannya.