Bagaimana bisa? Bukankah memori variabel lokal tidak dapat diakses di luar fungsinya?
Anda menyewa kamar hotel. Anda meletakkan buku di laci atas meja samping tempat tidur dan pergi tidur. Anda memeriksa keesokan paginya, tetapi "lupa" untuk mengembalikan kunci Anda. Anda mencuri kuncinya!
Seminggu kemudian, Anda kembali ke hotel, jangan check-in, menyelinap ke kamar lama Anda dengan kunci curian Anda, dan mencari di laci. Buku Anda masih ada di sana. Mengherankan!
Bagaimana itu bisa terjadi? Bukankah isi laci kamar hotel tidak dapat diakses jika Anda belum menyewa kamar?
Nah, jelas skenario itu bisa terjadi di dunia nyata tanpa masalah. Tidak ada kekuatan misterius yang menyebabkan buku Anda menghilang ketika Anda tidak lagi berwenang berada di ruangan itu. Juga tidak ada kekuatan misterius yang mencegah Anda memasuki ruangan dengan kunci curian.
Manajemen hotel tidak diharuskan untuk menghapus buku Anda. Anda tidak membuat kontrak dengan mereka yang mengatakan bahwa jika Anda meninggalkan barang di belakang, mereka akan menghancurkannya untuk Anda. Jika Anda secara ilegal memasuki kembali kamar Anda dengan kunci yang dicuri untuk mendapatkannya kembali, staf keamanan hotel tidak diharuskan menangkap Anda menyelinap masuk. Anda tidak membuat kontrak dengan mereka yang mengatakan "jika saya mencoba menyelinap kembali ke kamar saya kamar nanti, Anda diminta untuk menghentikan saya. " Sebaliknya, Anda menandatangani kontrak dengan mereka yang mengatakan "Saya berjanji untuk tidak menyelinap kembali ke kamar saya nanti", sebuah kontrak yang Anda pecahkan .
Dalam situasi ini apa pun bisa terjadi . Buku itu bisa ada di sana - Anda beruntung. Buku orang lain bisa ada di sana dan buku Anda bisa ada di tungku hotel. Seseorang bisa berada di sana tepat ketika Anda masuk, merobek-robek buku Anda. Hotel bisa saja menghapus meja dan memesan seluruhnya dan menggantinya dengan lemari pakaian. Seluruh hotel bisa saja akan dirobohkan dan diganti dengan stadion sepak bola, dan Anda akan mati dalam ledakan saat Anda menyelinap di sekitar.
Anda tidak tahu apa yang akan terjadi; ketika Anda keluar dari hotel dan mencuri kunci untuk digunakan secara ilegal nanti, Anda menyerahkan hak untuk hidup di dunia yang dapat diprediksi dan aman karena Anda memilih untuk melanggar aturan sistem.
C ++ bukan bahasa yang aman . Dengan riang akan memungkinkan Anda untuk melanggar aturan sistem. Jika Anda mencoba melakukan sesuatu yang ilegal dan bodoh seperti kembali ke ruangan yang tidak diizinkan untuk Anda masuki dan menggeledah meja yang bahkan mungkin tidak ada lagi, C ++ tidak akan menghentikan Anda. Bahasa yang lebih aman daripada C ++ menyelesaikan masalah ini dengan membatasi kekuatan Anda - dengan memiliki kontrol yang lebih ketat terhadap kunci, misalnya.
MEMPERBARUI
Ya ampun, jawaban ini mendapat banyak perhatian. (Saya tidak yakin mengapa - saya menganggapnya sebagai analogi kecil yang "menyenangkan", tetapi apa pun.)
Saya pikir mungkin akan sedikit lebih baik untuk memperbaruinya dengan beberapa pemikiran teknis.
Kompiler dalam bisnis menghasilkan kode yang mengelola penyimpanan data yang dimanipulasi oleh program itu. Ada banyak cara berbeda untuk menghasilkan kode untuk mengelola memori, tetapi seiring waktu dua teknik dasar telah mengakar.
Yang pertama adalah memiliki semacam area penyimpanan "berumur panjang" di mana "masa pakai" setiap byte dalam penyimpanan - yaitu, periode waktu ketika dikaitkan secara sah dengan beberapa variabel program - tidak dapat dengan mudah diprediksi ke depan waktu. Kompiler menghasilkan panggilan menjadi "heap manager" yang tahu bagaimana mengalokasikan penyimpanan secara dinamis saat diperlukan dan mengklaimnya kembali saat tidak lagi diperlukan.
Metode kedua adalah memiliki area penyimpanan "berumur pendek" di mana masa pakai setiap byte dikenal. Di sini, masa hidup mengikuti pola "bersarang". Variabel yang berumur pendek paling lama akan dialokasikan sebelum variabel berumur pendek lainnya, dan akan dibebaskan terakhir. Variabel berumur pendek akan dialokasikan setelah variabel berumur panjang, dan akan dibebaskan sebelum mereka. Masa hidup dari variabel-variabel yang berumur pendek ini adalah "bersarang" dalam masa hidup variabel-variabel yang berumur lebih panjang.
Variabel lokal mengikuti pola yang terakhir; ketika suatu metode dimasukkan, variabel lokalnya menjadi hidup. Ketika metode itu memanggil metode lain, variabel lokal metode baru menjadi hidup. Mereka akan mati sebelum variabel lokal metode pertama mati. Urutan relatif dari awal dan akhir masa pakai penyimpanan yang terkait dengan variabel lokal dapat diselesaikan sebelumnya.
Untuk alasan ini, variabel lokal biasanya dihasilkan sebagai penyimpanan pada struktur data "stack", karena stack memiliki properti yang didorong oleh hal pertama yang akan menjadi hal terakhir yang muncul.
Ini seperti hotel memutuskan untuk hanya menyewakan kamar secara berurutan, dan Anda tidak dapat check-out sampai semua orang dengan nomor kamar lebih tinggi dari yang Anda periksa.
Jadi mari kita pikirkan tentang stack. Dalam banyak sistem operasi, Anda mendapatkan satu tumpukan per utas dan tumpukan dialokasikan untuk ukuran tetap tertentu. Ketika Anda memanggil suatu metode, barang-barang didorong ke tumpukan. Jika Anda kemudian meneruskan sebuah pointer ke stack kembali dari metode Anda, seperti yang dilakukan poster asli di sini, itu hanyalah sebuah pointer ke tengah beberapa blok memori jutaan byte yang sepenuhnya valid. Dalam analogi kami, Anda check out dari hotel; ketika Anda melakukannya, Anda baru saja keluar dari kamar yang ditempati nomor tertinggi. Jika tidak ada orang lain yang check-in setelah Anda, dan Anda kembali ke kamar Anda secara ilegal, semua barang Anda dijamin masih ada di hotel ini .
Kami menggunakan tumpukan untuk toko sementara karena sangat murah dan mudah. Implementasi C ++ tidak diperlukan untuk menggunakan tumpukan untuk penyimpanan penduduk setempat; bisa menggunakan heap. Tidak, karena itu akan membuat program lebih lambat.
Implementasi C ++ tidak diperlukan untuk membiarkan sampah yang Anda tinggalkan di tumpukan tidak tersentuh sehingga Anda dapat kembali lagi nanti secara ilegal; sangat legal bagi kompiler untuk membuat kode yang mengembalikan semua nol pada "ruang" yang baru saja Anda tinggalkan. Bukan karena lagi, itu akan mahal.
Implementasi C ++ tidak diperlukan untuk memastikan bahwa ketika tumpukan menyusut secara logis, alamat yang dulu valid masih dipetakan ke dalam memori. Implementasinya diizinkan untuk memberi tahu sistem operasi "kami selesai menggunakan halaman stack ini sekarang. Sampai saya katakan sebaliknya, keluarkan pengecualian yang menghancurkan proses jika ada yang menyentuh halaman stack yang sebelumnya valid". Sekali lagi, implementasi tidak benar-benar melakukan itu karena lambat dan tidak perlu.
Sebaliknya, implementasi membuat Anda melakukan kesalahan dan lolos begitu saja. Sebagian besar waktu. Hingga suatu hari terjadi sesuatu yang sangat buruk dan prosesnya meledak.
Ini bermasalah. Ada banyak aturan dan sangat mudah untuk melanggarnya secara tidak sengaja. Saya sudah pasti berkali-kali. Dan lebih buruk lagi, masalah sering hanya muncul ketika memori terdeteksi menjadi milyaran milidetik rusak setelah korupsi terjadi, ketika sangat sulit untuk mencari tahu siapa yang mengacaukannya.
Lebih banyak bahasa yang aman-memori menyelesaikan masalah ini dengan membatasi daya Anda. Dalam "normal" C # tidak ada cara untuk mengambil alamat lokal dan mengembalikannya atau menyimpannya untuk nanti. Anda dapat mengambil alamat lokal, tetapi bahasa tersebut dirancang dengan cerdik sehingga tidak mungkin untuk menggunakannya setelah masa pakai lokal berakhir. Untuk mengambil alamat lokal dan mengembalikannya, Anda harus meletakkan kompiler dalam mode "tidak aman" khusus, dan meletakkan kata "tidak aman" di program Anda, untuk menarik perhatian pada fakta bahwa Anda mungkin melakukan sesuatu yang berbahaya yang bisa melanggar aturan.
Untuk bacaan lebih lanjut:
address of local variable ‘a’ returned
; valgrind showsInvalid write of size 4 [...] Address 0xbefd7114 is just below the stack ptr