Struct yang dikirim ke sebuah fungsi akan disalin. Itu tidak masalah untuk titik berisi dua int, tetapi boros untuk record berukuran 200 byte. Selain itu, fungsi jadi tidak bisa mengubahnya, karena fungsi itu hanya melihat salinannya sendiri. Menunjuk ke struct menyelesaikan kedua masalah sekaligus, dan itu juga satu-satunya cara membangun struktur yang bisa tumbuh: list, tree, dan graf.
Mengambil Pointer ke Struct
Tidak perlu apa pun yang khusus. & memberimu alamatnya, dan tipenya adalah "pointer ke struct":
Ketiganya mencetak 3. Dua yang terakhir adalah operasi yang sama, hanya ditulis dengan dua cara.
Kenapa (*ptr).x Butuh Tanda Kurung
Kamu mungkin mencoba membuang tanda kurungnya dan menulis *ptr.x. Hasil kompilasinya jauh berbeda, atau lebih sering malah tidak bisa di-compile sama sekali, karena operator anggota . punya presedensi lebih tinggi daripada dereference *:
*ptr.x // parses as *(ptr.x) -- wrong
(*ptr).x // dereference first, then take the member -- correct
ptr->x // the same thing, said shortly
ptr.x meminta anggota dari pointer-nya, padahal pointer tidak punya anggota. Keluhan compiler ("request for member 'x' in something not a structure or union") sebenarnya adalah bug presedensi yang menyamar.
Karena bentuk dengan tanda kurung itu jelek dan mudah salah, C menyediakan ->. Dalam praktik, kamu hampir tidak akan pernah melihat (*p).x di kode sungguhan. Pakai p->x dan lupakan saja bentuk panjangnya, kecuali sebagai penjelasan arti operator panah.
Mengirim Struct Lewat Pointer
Parameter pointer memberi fungsi akses ke struct asli milik pemanggil, jadi perubahannya bertahan:
Dua kebiasaan yang layak dibentuk di sini:
- Tandai pointer yang hanya dibaca dengan
const.const struct Point *pmenjanjikan bahwa fungsi itu tidak akan mengubah apa yang ditunjukp. Jika nanti ada yang menambahkanp->x = 0di dalamnya, kompilasi akan gagal alih-alih bug-nya ikut dirilis. Ini juga menjelaskan maksud kode di tempat pemanggilan. - Kirim pointer untuk struct besar, bahkan saat hanya membaca. Struct dengan buffer nama 64 byte memakan 64 byte penyalinan setiap kali dipanggil sebagai nilai; pointer hanya 8.
Struct kecil tetap boleh dikirim sebagai nilai. struct Point yang berukuran 8 byte sama murahnya untuk disalin seperti pointer, dan versi nilai tidak mungkin NULL, sehingga satu jenis kegagalan hilang sepenuhnya.
Pointer ke Dalam Array Struct
Aritmetika pointer bekerja pada array struct persis seperti pada array int: p + 1 maju sejauh satu struct utuh, termasuk padding-nya.
Nama array meluruh (decay) menjadi pointer ke elemen pertamanya, jadi staff sudah berupa struct Employee * saat dipanggil. Itu juga alasan fungsi tersebut butuh n yang dikirim terpisah: sizeof di dalam fungsi akan mengukur pointer-nya, bukan array-nya.
Mengalokasikan Struct di Heap
Struct di stack mati di akhir scope-nya. Untuk membuat struct yang hidup lebih lama daripada fungsi yang membuatnya, atau untuk membuat sebanyak yang diminta input, alokasikan dengan malloc:
Empat detail dalam fungsi kecil itu semuanya penting:
sizeof *e, bukansizeof(struct Employee). Bacanya "ukuran dari apa pun yang ditunjuke", jadi jika tipenya suatu saat berubah, alokasinya otomatis ikut. Keduanya tidak mungkin tidak sinkron.- Cek
NULL.mallocmengembalikanNULLjika tidak bisa memenuhi permintaan. Menulise->idlewat null pointer adalah segmentation fault. - Inisialisasi setiap anggota.
malloctidak mengisi memori dengan nol; struct-nya datang penuh nilai sampah. (calloc(1, sizeof *e)mengisinya dengan nol untukmu.) - Harus ada yang memanggil
free. Mengembalikan pointer hasil alokasi berarti menyerahkan tugas itu ke pemanggil. Tulis itu di komentar, karena aturan kepemilikan yang tidak jelas adalah awal mula memory leak.
Hasilnya: Node Linked List
Inilah hal yang tidak bisa dilakukan struct tanpa pointer. Struct tidak bisa berisi dirinya sendiri, karena ukurannya akan tak terhingga, tetapi bisa berisi pointer ke struct sejenis, dan satu trik itu membangun setiap struktur data berantai di C.
Perhatikan bahwa struct Node *next; merujuk ke struct Node saat struct Node masih sedang dideklarasikan. Itu sah justru karena ukuran pointer sudah diketahui, apa pun yang ditunjuknya, jadi compiler belum butuh definisi lengkapnya. Ini juga satu-satunya tempat kamu wajib memakai tag struct Node walaupun sudah menulis typedef: nama typedef belum ada di dalam definisinya sendiri.
Perulangan free_list menyimpan head->next sebelum memanggil free(head). Membaca head->next setelah free adalah use-after-free: memorinya bukan milikmu lagi, dan nilai yang kamu baca adalah apa pun yang ditaruh allocator di sana.
Kesalahan Umum
p->xpada pointer yang belum diinisialisasi atauNULL. Penyebab crash paling umum di kode yang banyak memakai struct. Inisialisasi pointer denganNULLdan cek sebelum melakukan dereference.- Mengembalikan pointer ke struct lokal.
struct Point *bad(void) { struct Point p = {1,2}; return &p; }mengembalikan alamat memori yang lenyap begitu fungsi kembali. Kembalikan struct-nya sebagai nilai, atau alokasikan. - Membebaskan dua kali, atau lupa membebaskan. Setiap
mallocberpasangan dengan tepat satufree. Setelah membebaskan, isi pointer-nya denganNULLsupaya pemakaian tak sengaja di kemudian hari langsung crash dengan jelas, alih-alih diam-diam merusak memori. - Menyalin struct yang berisi pointer. Assignment struct menyalin nilai pointer-nya, bukan apa yang ditunjuknya. Kedua struct kini merujuk ke buffer yang sama, dan mana pun yang lebih dulu membebaskannya akan membuat yang lain menjadi dangling.
Pertanyaan yang sering diajukan
Apa fungsi operator -> di C?
p->x mengakses anggota x lewat pointer p. Bentuk ini persis sama dengan (*p).x: dereference pointer-nya, lalu ambil anggotanya. Operator panah ada karena pointer ke struct begitu umum sehingga bentuk panjangnya membuat kode berantakan.
Kenapa *p.x tidak berfungsi untuk pointer struct?
Karena . mengikat lebih kuat daripada *. *p.x dibaca sebagai *(p.x): ambil anggota x dari p (yang merupakan pointer, bukan struct) lalu dereference hasilnya. Kamu butuh (*p).x, atau cukup tulis p->x.
Sebaiknya struct dikirim sebagai nilai atau lewat pointer di C?
Kirim lewat pointer jika fungsinya harus mengubah struct milik pemanggil, atau jika struct-nya besar dan menyalinnya akan boros. Kirim sebagai nilai untuk struct kecil yang hanya dibaca, karena salinannya murah dan kodenya lebih sederhana. Tandai parameter pointer yang hanya dibaca dengan const struct T * supaya compiler menegakkan janji itu.
Bagaimana cara mengalokasikan struct dengan malloc di C?
struct Node *n = malloc(sizeof *n);. Bentuk sizeof *n meminta ukuran dari apa pun yang ditunjuk n, jadi tetap benar jika nanti kamu mengganti tipenya. Cek hasilnya terhadap NULL, inisialisasi setiap anggota, dan panggil free(n) setelah selesai.