Untuk benar-benar memahami mengapa pointer berperilaku seperti itu, Anda harus berhenti berpikir dalam variabel abstrak dan mulai memvisualisasikan perangkat keras. Ini adalah model yang sederhana, namun merupakan dasar dari segalanya, mulai dari segfault hingga kerentanan keamanan. Jika Anda bingung dengan dasar-dasar bit, byte, dan kata, Anda mungkin harus berhenti sejenak di sini dan memolesnya terlebih dahulu. Penjelasan selanjutnya ini mengasumsikan Anda mengetahui perbedaan antara byte dan bit.
Komputer Anda memiliki memori, atau RAM (memori akses acak). Anda mungkin menginstal 16, 32, atau 64 gigabyte. Ini bukan hanya sekedar penyimpanan; itu ruang kerja. Ini menyimpan kode yang sedang dijalankan CPU Anda dan data yang dimanipulasi oleh program tersebut tepat pada detik itu. Bayangkan RAM sebagai array byte yang sangat besar dan linier.
Setiap byte dalam array itu memiliki alamat. Byte pertama adalah alamat 0. Byte berikutnya adalah 1. Lalu 2, 3, dan seterusnya. Alamat-alamat ini berfungsi persis seperti indeks dalam array pemrograman standar. Karena CPU dapat berpindah ke alamat mana pun secara instan, ini disebut “memori akses acak”. Tidak harus membaca secara berurutan. Ia dapat mengambil apa pun yang dibutuhkannya, kapan pun ia membutuhkannya. Ketika kompiler perlu menyimpan tipe data yang lebih besar, kompiler hanya mengambil satu blok byte yang berdekatan. Nomor floating-point standar, misalnya, biasanya membutuhkan 4 byte yang berdekatan.
Mari kita lihat deklarasi global di C:
Bagi Anda, ini adalah variabel bernama f yang menampung float. Bagi komputer, ini adalah perintah untuk mencadangkan 4 byte tertentu dalam susunan memori. Katakanlah kompiler menugaskan byte ini ke lokasi memori 248.440.
Saat Anda menulis:
Kompiler tidak berpikir “perbarui variabel f”. Ia berpikir “muat nilai 3,14 ke alamat memori 248.440.” Abstraksinya tipis. Di balik terpalnya, ini semua tentang alamat dan nilai-nilai yang ada di alamat tersebut.
Sifat mekanis dari memori ini menyebabkan beberapa efek samping yang buruk. Pertimbangkan cuplikan kode C ini:
Anda mungkin mengharapkan s dan t menahan 0:0, 1:1, 2:2, 3:3. Dan u seharusnya tetap 0. Sebaliknya, outputnya terlihat seperti ini:
Tunggu. Apa yang terjadi dengan t[0]? Dan mengapa u sekarang 5?
Masalahnya adalah buffer overflow klasik. Perhatikan baik-baik kondisi loop: i<=4. Array t dideklarasikan sebagai t[4], artinya ia memiliki indeks 0, 1, 2, dan 3. Ketika i mencapai 4, kode menulis ke t[4]. Indeks tersebut tidak ada dalam ruang yang dialokasikan untuk t. Itu menulis satu elemen melewati akhir array.
Karena memori bersebelahan, komputer menempatkan s, t, dan u tepat bersebelahan dalam heap atau tumpukan.
Saat Anda menulis melewati akhir array, Anda tidak langsung crash. Anda menimpa apa pun yang ada di slot memori tepat di sebelahnya.
Dalam kasus khusus ini, menulis ke t[4] akan menimpa lokasi memori yang menyimpan u (yang awalnya 0). Tapi kenapa `u

Tulis ke s[4]. Indeks itu tidak ada. Tapi komputer tidak peduli. Itu tidak memeriksa apakah Anda menginjak kaki Anda sendiri. Itu hanya menghitung alamatnya. Dan alamat itu kebetulan mendarat di t[0].
Anda pikir Anda sedang menulis ke s. Anda sebenarnya menulis ke t. Sistem menjalankan perintah tanpa berkedip. Logikanya salah. Program ini merusak data. Itu diam sampai tidak ada lagi.
Sekarang cobalah sesuatu yang lebih buruk.
s[1000000] = 5;
Anda menulis ke memori yang tidak dimiliki program Anda. Konsekuensinya bergantung sepenuhnya pada sistem operasi yang Anda jalankan. Pada sistem yang dilindungi seperti UNIX, Windows NT, atau Windows 98, OS langsung melihat pelanggaran ini. Ini menghentikan program. Berhenti sulit. Ini melindungi seluruh sistem dari kesalahan Anda.
Sistem lama seperti Windows 3.1 atau Mac OS klasik tidak memiliki kemewahan itu. Mereka tidak tahu apa yang Anda lakukan. Anda akhirnya menimpa kode atau variabel di aplikasi lain. Hasilnya? Sebuah kesalahan. Kecelakaan. Atau lebih buruk lagi, kegagalan seluruh sistem yang terasa acak namun sebenarnya merupakan akibat langsung dari akses memori yang tidak terkendali.
Dalam memori, variabel seperti i, s, t, dan u ditempatkan bersebelahan pada alamat tertentu. Mereka adalah tetangga. Jika Anda menulis melewati batas yang satu, Anda akan meluas ke batas berikutnya. Komputer melakukan persis seperti yang Anda katakan. Tidak masalah jika Anda bermaksud baik.
Bahaya Akses Array yang Tidak Dicentang
C dan C++ tidak melakukan pemeriksaan jangkauan. Mereka menganggap Anda tahu apa yang Anda lakukan. Ini berarti Anda harus memperhatikan rentang array. Jika Anda membaca atau menulis di luar batasan, perilaku tersebut salah. Tidak dapat diprediksi. Berbahaya.
Kurangnya keamanan inilah yang menyebabkan kesalahan penunjuk yang tidak diinisialisasi sangat umum terjadi di C. Pertimbangkan kode ini:
Kompiler mencetak nilai dalam p dan alamat i. Awalnya, p menampung sampah atau nol. Alamat i biasanya berupa angka yang besar. Misalnya, Anda mungkin melihat:
Setelah p = &i, p menyimpan alamat i. Sederhana.
Sekarang lihat ini:
Ini mencetak nilai yang ditunjuk p. Tapi p tidak diinisialisasi. Ini menunjuk ke alamat 0 atau bit memori acak. Hasilnya hampir selalu berupa kesalahan segmentasi. Kesalahan waktu proses. Anda mencoba mengakses memori yang bukan milik Anda.
Petunjuk dalam Cahaya Baru
Setelah Anda memahami risiko ini, petunjuknya berubah. Itu bukan sekedar variabel. Itu adalah alamat langsung ke lokasi memori.
Ambil program ini:
Inilah yang terjadi:
Bagaimana Pointer Menyimpan Alamat Memori
Saat Anda mendeklarasikan variabel integer seperti i, dibutuhkan ruang sebesar 4 byte di RAM. Sebuah pointer, sebut saja p, juga menempati 4 byte. Ukuran ini berlaku untuk sebagian besar sistem yang beredar saat ini, dimana alamat memori panjangnya 32 bit. Industri ini perlahan-lahan beralih ke pengalamatan 64-bit, namun untuk saat ini, ukuran 4-byte adalah standarnya.
Bayangkan i sebagai sebuah rumah. Ini memiliki alamat jalan tertentu—misalnya, 248.440. Penunjuk p bukanlah rumah itu sendiri. Itu adalah selembar kertas dengan alamat tertulis di atasnya. Saat Anda menetapkan p = &i, Anda menulis 248.440 di kertas itu.
Perbedaan ini sangat penting untuk memahami cara kerja pointer di C. Penunjuk tidak memiliki nilai i. Ini memegang lokasi di mana i tinggal. Jika Anda melakukan dereferensi p (menggunakan p *), Anda akan menuju ke alamat itu dan membaca nilainya. Dalam hal ini, p dan i * secara fungsional identik. Mereka menunjuk pada data yang sama.
Melihat Alamat dalam Kode
Anda dapat memverifikasi perilaku ini dengan pernyataan print sederhana.
printf("%d",p);
Perintah ini tidak mencetak nilai i. Ini mencetak nomor yang tertulis di kertas. Outputnya adalah alamat memori sebenarnya i.
Mengapa ini penting? Karena ini memungkinkan Anda meneruskan lokasi, bukan salinan data. Anda dapat memanipulasi memori secara langsung. Anda dapat menghubungkan struktur bersama-sama. Anda dapat mengelola memori heap secara efisien. Pointer adalah referensi. Variabelnya adalah konten. Membingungkan keduanya akan menyebabkan bug. Memahami perbedaan mengarah pada pengendalian.
Beberapa pengembang memperlakukan pointer sebagai keajaiban. Sebenarnya tidak. Itu hanyalah angka. Alamat memori adalah bilangan bulat. Perlakukan mereka seperti itu.

















