Menu

Pointer ke Struct di C: Operator Panah dan Struct di Heap

Cara menunjuk ke struct di C: operator panah, kenapa (*p).x butuh tanda kurung, mengirim struct lewat pointer untuk mengubahnya atau menghindari salinan, mengalokasikan struct dengan malloc, dan membuat node linked list.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

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 *p menjanjikan bahwa fungsi itu tidak akan mengubah apa yang ditunjuk p. Jika nanti ada yang menambahkan p->x = 0 di 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, bukan sizeof(struct Employee). Bacanya "ukuran dari apa pun yang ditunjuk e", jadi jika tipenya suatu saat berubah, alokasinya otomatis ikut. Keduanya tidak mungkin tidak sinkron.
  • Cek NULL. malloc mengembalikan NULL jika tidak bisa memenuhi permintaan. Menulis e->id lewat null pointer adalah segmentation fault.
  • Inisialisasi setiap anggota. malloc tidak 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->x pada pointer yang belum diinisialisasi atau NULL. Penyebab crash paling umum di kode yang banyak memakai struct. Inisialisasi pointer dengan NULL dan 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 malloc berpasangan dengan tepat satu free. Setelah membebaskan, isi pointer-nya dengan NULL supaya 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.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai