Bagaimana Sebenarnya C Pointer Bekerja: Penjelasan Alamat Memori

13

Kebanyakan pengembang memperlakukan memori seperti kotak hitam. Mereka menganggap variabel hanyalah nama yang melekat pada nilai. Itu berfungsi sampai Anda perlu menulis kode yang memanipulasi data secara langsung di tingkat mesin. Kemudian Anda menekan petunjuk.

Variabel normal itu sederhana. Itu adalah sepotong memori dengan nama yang Anda gunakan dalam kode Anda. Deklarasikan bilangan bulat i, dan kompiler mencadangkan empat byte. Anda melihat i. Mesin melihat alamat. Nilainya berada di empat byte tersebut.

Pointer mematahkan model sederhana itu.

Sebuah pointer tidak memiliki nilai. Itu berisi alamat. Ini menunjuk ke variabel lain. Secara khusus, ini menyimpan lokasi memori variabel tersebut. Perbedaan ini sangat penting. Jika Anda mengacaukan keduanya, program Anda akan crash. Keras.

Strukturnya berlapis ganda. Pertama, ada variabel pointer itu sendiri. Ini berisi alamat numerik. Kedua, ada variabel target. Alamat di penunjuk mengarahkan Anda ke data sebenarnya. Setelah Anda memahami pemisahan ini, Anda membuka kekuatan signifikan dalam pemrograman sistem.

Pertimbangkan contoh standar C ini:

Baris pertama mendeklarasikan dua bilangan bulat standar, i dan j. Baris berikutnya, int *p, mendeklarasikan sebuah pointer. Tanda bintang adalah kuncinya. Ini memberi tahu kompiler bahwa p bukan bilangan bulat. Ini adalah penunjuk ke bilangan bulat. Anda dapat melakukan ini dengan pelampung, struktur, karakter, atau apa pun. Sintaksnya tetap sama.

Kemudian muncul bagian yang membuat pemula tersandung: p = &i;.

Di C, ampersand & adalah operator alamat. &i tidak mengembalikan nilai i. Ini mengembalikan alamat memori tempat i tinggal. Tugas p = &i menyalin alamat itu ke penunjuk p. Sekarang p menunjuk ke i.

Jika Anda melewatkan langkah ini, p menampung sampah. Ini berisi alamat acak dan tidak ditentukan. Dereferensi nanti kemungkinan besar akan menyebabkan kesalahan segmentasi. Program mogok karena Anda menyuruhnya mengakses memori yang bukan miliknya.

Visualisasi membantu. Bayangkan memori sebagai sebuah grid.

Sebelum inisialisasi, i, j, dan p ada tetapi memiliki nilai yang tidak diketahui. Bilangan bulat adalah kotak dengan tanda tanya. Mereka bisa berisi apa saja. Penunjuk p berbentuk lingkaran. Ia memiliki panah yang menunjuk ke arah acak. Ia belum tahu ke mana harus pergi.

Setelah Anda menjalankan p = &i, panahnya berubah. Semuanya menunjuk langsung ke i. p sekarang tahu persis di mana i tinggal.

Hubungannya jelas. p menyimpan lokasi. i menyimpan data. Anda menggunakan p untuk mencapai i.

Mekanisme ini adalah bagaimana C mengelola memori secara efisien. Ini memungkinkan fungsi untuk mengubah variabel yang diteruskan dengan referensi. Ini memungkinkan alokasi memori dinamis. Ini memungkinkan Anda membangun struktur data yang kompleks seperti daftar tertaut dan pohon.

Namun hal ini juga menimbulkan risiko. Satu alamat salah, dan Anda membaca atau menulis ke dalam kehampaan.

Tanda bintang di *p = 5 melakukan sesuatu yang berbeda dari tanda bintang di int *p. Tanda bintang kedua adalah bagian dari definisi tipe. Itu mendeklarasikan variabel sebagai pointer. Tanda bintang pertama adalah operator dereferensi. Artinya “buka alamat yang disimpan di p dan ubah nilainya di sana.”

Jadi *p = 5 memasukkan angka 5 ke lokasi memori yang ditunjuk oleh p. Karena p menunjuk ke i, i menjadi 5. Variabel j kemudian diberi nilai i. Outputnya mencetak 5, 5, dan 5. Ketiganya mencerminkan perubahan mendasar yang sama.

Memahami cara kerja pointer C memerlukan perubahan pola pikir Anda. Berhentilah menganggap variabel sebagai wadah. Mulailah menganggapnya sebagai lokasi. Penunjuknya adalah peta. Variabelnya adalah tujuannya. Jika petanya salah, Anda akhirnya tersesat. Dan dalam pemrograman tingkat rendah, tersesat itu mahal.

Operator Tipuan Dijelaskan

Saat Anda menetapkan alamat i ke p, pada dasarnya Anda memberi satu blok memori itu dua nama berbeda. Variabel i masih merujuk secara langsung, namun p sekarang berfungsi sebagai pegangan, titik referensi untuk mencapai data yang sama.

Di C, dualitas ini mendefinisikan bagaimana variabel penunjuk berfungsi. Pointer itu sendiri, p, hanyalah sebuah wadah yang menyimpan sebuah alamat. Itu tidak berisi nilai sebenarnya yang Anda minati. Sebaliknya, itu berisi lokasi di mana nilai itu berada. Di sinilah operator tipuan * berperan.

Menggunakan *p memungkinkan Anda mengakses data di alamat yang disimpan di p. Anggap saja sebagai dereferensi. Jika Anda menulis *p = 5, Anda tidak mengubah alamat di dalam p. Anda pergi ke lokasi yang ditunjuk p dan menimpa nilai apa pun yang ada di sana dengan angka 5.

Perbedaan ini sangat penting untuk memahami manajemen memori. p memberi tahu Anda di mana mencarinya. *p memberi tahu Anda apa yang harus dilihat. Membingungkan keduanya akan menyebabkan bug yang sulit dilacak, karena Anda mungkin akan mengubah alamat alih-alih data, atau mencoba menulis ke lokasi memori acak karena p tidak diinisialisasi dengan benar.

Setelah Anda merasa nyaman dengan model mental ini, pointer tidak lagi menjadi sintaksis abstrak dan menjadi jalur langsung ke memori. Anda dapat memodifikasi variabel di berbagai cakupan, meneruskan struktur besar dengan referensi tanpa menyalinnya, dan membangun struktur data dinamis yang tumbuh dan menyusut sesuai kebutuhan. Ini adalah hal yang hebat, setelah Anda berhenti melawan sintaksisnya.

Cuplikan kode di bawah mengilustrasikan perilaku persis ini, menunjukkan bagaimana nilai i berubah hanya karena p digunakan untuk mencapainya.

Nilai i diperbarui secara instan. Alamatnya belum berpindah. Hanya kontennya saja yang memilikinya.

Bagaimana Pointer Menyimpan Nilai dan Alamat

Tampaknya cukup sederhana, tetapi mekanismenya dapat membuat Anda tersandung. Karena variabel i diberi nilai 5, dan penunjuk p berisi alamat i, dereferensi p (*p ) akan menghasilkan 5. Saat Anda mengeksekusi j = i;, Anda menyalin nilai tersebut. Jadi j menjadi 5.

Jalankan pernyataan printf sekarang. Anda mendapatkan 5 5 5.

Hal ini terjadi karena sifat ganda dari pointer. Anggap saja mereka memiliki dua peran berbeda. Pertama, variabel pointer itu sendiri menyimpan alamat memori. Kedua, ini menunjuk ke tipe data tertentu yang terletak di alamat itu.

Dalam contoh kita:
p adalah penunjuknya. Ini menyimpan lokasi memori.
*p adalah nilainya. Ini adalah data aktual yang disimpan di alamat itu.

Membingungkan keduanya akan menyebabkan bug. Jika Anda memperlakukan alamat sebagai data, Anda mendapatkan nilai sampah. Jika Anda memperlakukan data sebagai alamat, program Anda akan crash. Mengetahui mana yang Anda hadapi mengubah segalanya.

Mengapa perbedaan ini penting dalam pengkodean sehari-hari? Karena memahami perbedaan antara tempat data berada dan data apa yang ada memungkinkan Anda memanipulasi memori secara langsung. Ini berguna untuk struktur dinamis, namun berbahaya jika Anda melewatkan langkahnya. Satu langkah salah dan sistem mencoba membaca dari lokasi yang tidak ada.

Anda mulai memahami mengapa C tetap kuat dan berbahaya dalam ukuran yang sama.