Untuk memberikan contoh konkret tentang bagaimana kompiler mengelola tumpukan dan bagaimana nilai-nilai pada tumpukan diakses, kita dapat melihat penggambaran visual, ditambah kode yang dihasilkan oleh GCC
dalam lingkungan Linux dengan i386 sebagai arsitektur target.
1. Tumpuk frame
Seperti yang Anda ketahui, stack adalah lokasi di ruang alamat dari proses yang berjalan yang digunakan oleh fungsi , atau prosedur , dalam arti bahwa ruang dialokasikan pada stack untuk variabel yang dideklarasikan secara lokal, serta argumen yang dikirimkan ke fungsi ( ruang untuk variabel yang dideklarasikan di luar fungsi apa pun (yaitu variabel global) dialokasikan di wilayah yang berbeda dalam memori virtual). Ruang yang dialokasikan untuk semua data fungsi dirujuk ke bingkai tumpukan . Berikut ini adalah gambaran visual dari beberapa frame stack (dari Computer Systems: A Programmer's Perspective ):
2. Manajemen bingkai tumpukan dan lokasi variabel
Agar nilai yang ditulis ke tumpukan dalam bingkai tumpukan tertentu untuk dikelola oleh kompiler dan dibaca oleh program, harus ada beberapa metode untuk menghitung posisi nilai-nilai ini dan mengambil alamat memori mereka. Register dalam CPU disebut sebagai stack pointer dan bantuan pointer dasar dengan ini.
Pointer basis, ebp
berdasarkan konvensi, berisi alamat memori bagian bawah, atau bagian bawah, dari tumpukan. Posisi semua nilai dalam bingkai tumpukan dapat dihitung menggunakan alamat pada penunjuk dasar sebagai referensi. Ini digambarkan dalam gambar di atas: %ebp + 4
adalah alamat memori yang disimpan dalam pointer dasar plus 4, misalnya.
3. Kode yang dihasilkan kompiler
Tapi yang tidak saya dapatkan adalah bagaimana variabel pada stack kemudian dibaca oleh aplikasi - jika saya mendeklarasikan dan menetapkan x sebagai integer, katakanlah x = 3, dan penyimpanan dicadangkan pada stack dan kemudian nilainya 3 disimpan di sana, dan kemudian dalam fungsi yang sama saya menyatakan dan menetapkan y sebagai, katakanlah 4, dan kemudian setelah itu saya kemudian menggunakan x dalam ekspresi lain, (katakanlah z = 5 + x) bagaimana program dapat membaca x untuk mengevaluasi z saat mengevaluasi di bawah y di tumpukan?
Mari kita gunakan contoh program sederhana yang ditulis dalam C untuk melihat bagaimana ini bekerja:
int main(void)
{
int x = 3;
int y = 4;
int z = 5 + x;
return 0;
}
Mari kita periksa teks rakitan yang diproduksi oleh GCC untuk teks sumber C ini (saya membersihkannya sedikit demi kejelasan):
main:
pushl %ebp # save previous frame's base address on stack
movl %esp, %ebp # use current address of stack pointer as new frame base address
subl $16, %esp # allocate 16 bytes of space on stack for function data
movl $3, -12(%ebp) # variable x at address %ebp - 12
movl $4, -8(%ebp) # variable y at address %ebp - 8
movl -12(%ebp), %eax # write x to register %eax
addl $5, %eax # x + 5 = 9
movl %eax, -4(%ebp) # write 9 to address %ebp - 4 - this is z
movl $0, %eax
leave
Apa yang kami amati adalah bahwa variabel x, y dan z terletak di alamat %ebp - 12
, %ebp -8
dan %ebp - 4
, masing-masing. Dengan kata lain, lokasi variabel dalam bingkai tumpukan main()
dihitung menggunakan alamat memori yang disimpan dalam register CPU %ebp
.
4. Data dalam memori di luar penunjuk tumpukan berada di luar ruang lingkup
Saya jelas kehilangan sesuatu. Apakah itu lokasi pada stack hanya tentang masa hidup / lingkup variabel, dan bahwa seluruh stack sebenarnya dapat diakses oleh program sepanjang waktu? Jika demikian, apakah itu menyiratkan ada beberapa indeks lain yang hanya menyimpan alamat variabel di stack untuk memungkinkan nilai yang akan diambil? Tapi kemudian saya pikir seluruh poin stack adalah bahwa nilai disimpan di tempat yang sama dengan alamat variabel?
Tumpukan adalah wilayah dalam memori virtual, yang penggunaannya dikelola oleh kompiler. Compiler menghasilkan kode sedemikian rupa sehingga nilai-nilai di luar penunjuk tumpukan (nilai-nilai di atas tumpukan) tidak pernah direferensikan. Ketika suatu fungsi dipanggil, posisi penunjuk tumpukan berubah untuk menciptakan ruang pada tumpukan yang dianggap tidak "di luar batas", jadi bisa dikatakan.
Saat fungsi dipanggil dan kembali, penunjuk tumpukan dikurangi dan bertambah. Data yang ditulis ke tumpukan tidak hilang setelah di luar ruang lingkup, tetapi kompiler tidak menghasilkan instruksi yang merujuk data ini karena tidak ada cara bagi kompiler untuk menghitung alamat data ini menggunakan %ebp
atau %esp
.
5. Ringkasan
Kode yang dapat dieksekusi langsung oleh CPU dihasilkan oleh kompiler. Kompiler mengelola tumpukan, susunan bingkai untuk fungsi dan register CPU. Salah satu strategi yang digunakan oleh GCC untuk melacak lokasi variabel dalam bingkai tumpukan dalam kode yang dimaksudkan untuk dieksekusi pada arsitektur i386 adalah dengan menggunakan alamat memori dalam penunjuk basis bingkai tumpukan %ebp
,, sebagai referensi dan menulis nilai variabel ke lokasi dalam bingkai tumpukan di offset ke alamat di %ebp
.