Apa itu stack unwinding? Cari tetapi tidak menemukan jawaban yang mencerahkan!
Apa itu stack unwinding? Cari tetapi tidak menemukan jawaban yang mencerahkan!
Jawaban:
Stack unwinding biasanya dibicarakan sehubungan dengan penanganan pengecualian. Ini sebuah contoh:
void func( int x )
{
char* pleak = new char[1024]; // might be lost => memory leak
std::string s( "hello world" ); // will be properly destructed
if ( x ) throw std::runtime_error( "boom" );
delete [] pleak; // will only get here if x == 0. if x!=0, throw exception
}
int main()
{
try
{
func( 10 );
}
catch ( const std::exception& e )
{
return 1;
}
return 0;
}
Di sini memori yang dialokasikan untuk pleak
akan hilang jika pengecualian dilemparkan, sedangkan memori yang dialokasikan untuk s
akan dirilis dengan benar oleh std::string
destruktor dalam hal apa pun. Objek yang dialokasikan pada stack "dibatalkan" ketika ruang lingkup keluar (di sini ruang lingkup fungsi func
.) Ini dilakukan oleh kompiler memasukkan panggilan ke destruktor variabel otomatis (stack) variabel.
Sekarang ini adalah konsep yang sangat kuat yang mengarah ke teknik yang disebut RAII , yaitu Resource Acquisition Is Inisialisasi , yang membantu kita mengelola sumber daya seperti memori, koneksi database, deskriptor file terbuka, dll dalam C ++.
Sekarang memungkinkan kami untuk memberikan jaminan keamanan pengecualian .
delete [] pleak;
hanya tercapai jika x == 0.
Semua ini berhubungan dengan C ++:
Definisi : Ketika Anda membuat objek secara statis (di stack sebagai lawan mengalokasikannya di memori tumpukan) dan melakukan panggilan fungsi, mereka "ditumpuk".
Ketika ruang lingkup (apa pun yang dibatasi oleh {
dan }
) keluar (dengan menggunakan return XXX;
, mencapai akhir ruang lingkup atau melemparkan pengecualian) segala sesuatu dalam ruang lingkup tersebut dihancurkan (destruktor dipanggil untuk semuanya). Proses menghancurkan objek lokal dan memanggil destruktor disebut stack unwinding.
Anda memiliki masalah berikut yang terkait dengan tumpukan dibatalkan:
menghindari kebocoran memori (apa pun yang dialokasikan secara dinamis yang tidak dikelola oleh objek lokal dan dibersihkan di destruktor akan bocor) - lihat RAII yang dirujuk oleh Nikolai, dan dokumentasi untuk boost :: scoped_ptr atau contoh penggunaan boost :: mutex ini :: scoped_lock .
konsistensi program: spesifikasi C ++ menyatakan bahwa Anda tidak boleh melemparkan pengecualian sebelum pengecualian yang ada ditangani. Ini berarti bahwa proses pelonggaran stack tidak boleh melempar pengecualian (baik gunakan hanya kode yang dijamin tidak akan membuang destruktor, atau mengelilingi segala sesuatu di destruktor dengan try {
dan } catch(...) {}
).
Jika ada destructor yang melempar pengecualian selama stack unwinding Anda berakhir di tanah perilaku tidak terdefinisi yang dapat menyebabkan program Anda berhenti secara tak terduga (perilaku paling umum) atau alam semesta berakhir (secara teori mungkin tetapi belum diamati dalam praktiknya).
Secara umum, tumpukan "bersantai" cukup identik dengan akhir pemanggilan fungsi dan pemunculan tumpukan berikutnya.
Namun, khususnya dalam kasus C ++, stack unwinding berkaitan dengan bagaimana C ++ memanggil destruktor untuk objek yang dialokasikan sejak dimulainya blok kode apa pun. Objek yang dibuat dalam blok dideallocated dalam urutan terbalik alokasi mereka.
try
balok. Stack objek yang dialokasikan di blok mana pun (apakah try
atau tidak) dapat dibatalkan saat blok keluar.
Stack unwinding sebagian besar adalah konsep C ++, berurusan dengan bagaimana objek yang dialokasikan stack dihancurkan ketika cakupannya keluar (baik secara normal, atau melalui pengecualian).
Katakanlah Anda memiliki fragmen kode ini:
void hw() {
string hello("Hello, ");
string world("world!\n");
cout << hello << world;
} // at this point, "world" is destroyed, followed by "hello"
Saya tidak tahu apakah Anda pernah membaca ini, tetapi artikel Wikipedia tentang tumpukan panggilan memiliki penjelasan yang layak.
Tidak berliku:
Kembali dari fungsi yang dipanggil akan memunculkan frame atas dari tumpukan, mungkin meninggalkan nilai balik. Tindakan yang lebih umum yaitu mengeluarkan satu atau lebih frame dari stack untuk melanjutkan eksekusi di tempat lain dalam program ini disebut stack unwinding dan harus dilakukan ketika struktur kontrol non-lokal digunakan, seperti yang digunakan untuk penanganan pengecualian. Dalam hal ini, bingkai tumpukan fungsi berisi satu atau lebih entri yang menentukan penangan pengecualian. Ketika eksepsi dilemparkan, tumpukan dilepas sampai pawang ditemukan yang siap untuk menangani (menangkap) jenis pengecualian yang dilemparkan.
Beberapa bahasa memiliki struktur kontrol lain yang membutuhkan pelepasan secara umum. Pascal memungkinkan pernyataan goto global untuk mentransfer kontrol dari fungsi bersarang dan ke fungsi luar yang sebelumnya dipanggil. Operasi ini membutuhkan tumpukan untuk dibatalkan, menghapus sebanyak mungkin bingkai tumpukan untuk mengembalikan konteks yang tepat untuk mentransfer kontrol ke pernyataan target dalam fungsi luar yang melampirkan. Demikian pula, C memiliki fungsi setjmp dan longjmp yang bertindak sebagai goto non-lokal. Common Lisp memungkinkan kontrol atas apa yang terjadi ketika stack dilepas dengan menggunakan operator khusus pelepas-lindung.
Saat menerapkan kelanjutan, tumpukan (secara logis) dibatalkan dan kemudian digulung ulang dengan tumpukan kelanjutan. Ini bukan satu-satunya cara untuk mengimplementasikan kelanjutan; misalnya, dengan menggunakan banyak, tumpukan eksplisit, aplikasi lanjutan dapat dengan mudah mengaktifkan tumpukannya dan memutar nilai yang akan diteruskan. Bahasa pemrograman Skema memungkinkan thungks sewenang-wenang untuk dieksekusi di titik-titik yang ditentukan pada "unwinding" atau "rewinding" dari tumpukan kontrol ketika kelanjutan dipanggil.
Inspeksi [sunting]
Saya membaca posting blog yang membantu saya memahami.
Apa itu stack unwinding?
Dalam bahasa apa pun yang mendukung fungsi rekursif (mis., Hampir semuanya kecuali Fortran 77 dan Brainf * ck) runtime bahasa menyimpan setumpuk fungsi yang sedang dijalankan. Stack unwinding adalah cara memeriksa, dan mungkin memodifikasi, tumpukan itu.
Mengapa Anda ingin melakukan itu?
Jawabannya mungkin tampak jelas, tetapi ada beberapa situasi yang terkait, namun agak berbeda, di mana bersantai berguna atau diperlukan:
- Sebagai mekanisme aliran kontrol runtime (pengecualian C ++, C longjmp (), dll).
- Dalam debugger, untuk menampilkan tumpukan kepada pengguna.
- Dalam profiler, untuk mengambil sampel tumpukan.
- Dari program itu sendiri (seperti dari penangan kecelakaan untuk menunjukkan tumpukan).
Ini memiliki persyaratan yang agak berbeda. Beberapa di antaranya kritis terhadap kinerja, beberapa tidak. Beberapa memerlukan kemampuan untuk merekonstruksi register dari bingkai luar, beberapa tidak. Tapi kita akan membahas semua itu sebentar lagi.
Anda dapat menemukan pos lengkap di sini .
Semua orang telah berbicara tentang penanganan pengecualian di C ++. Tapi, saya pikir ada konotasi lain untuk stack unwinding dan itu terkait dengan debugging. Seorang debugger harus melakukan stack unwinding kapan pun ia seharusnya pergi ke frame sebelum ke frame saat ini. Namun, ini adalah semacam pelonggaran virtual karena perlu mundur ketika kembali ke bingkai saat ini. Contoh untuk ini bisa berupa perintah naik / turun / bt di gdb.
IMO, diagram di bawah ini yang diberikan dalam artikel ini dengan indah menjelaskan efek tumpukan unwinding pada rute instruksi berikutnya (akan dieksekusi setelah pengecualian dilemparkan yang tidak tertangkap):
Di dalam pic:
Dalam kasus kedua, ketika pengecualian terjadi, tumpukan panggilan fungsi secara linear mencari penangan pengecualian. Pencarian berakhir pada fungsi dengan handler pengecualian yaitu main()
dengan melampirkan try-catch
blok, tetapi tidak sebelum menghapus semua entri sebelum dari tumpukan panggilan fungsi.
C ++ runtime merusak semua variabel otomatis yang dibuat antara throw & catch. Dalam contoh sederhana di bawah ini, f1 () melempar dan tangkapan utama (), di antara objek tipe B dan A dibuat pada tumpukan dalam urutan itu. Ketika f1 () melempar, penghancur B dan A dipanggil.
#include <iostream>
using namespace std;
class A
{
public:
~A() { cout << "A's dtor" << endl; }
};
class B
{
public:
~B() { cout << "B's dtor" << endl; }
};
void f1()
{
B b;
throw (100);
}
void f()
{
A a;
f1();
}
int main()
{
try
{
f();
}
catch (int num)
{
cout << "Caught exception: " << num << endl;
}
return 0;
}
Output dari program ini adalah
B's dtor
A's dtor
Ini karena callstack program ketika f1 () melempar terlihat seperti
f1()
f()
main()
Jadi, ketika f1 () muncul, variabel otomatis b dihancurkan, dan kemudian ketika f () muncul variabel otomatis a dihancurkan.
Semoga ini bisa membantu, selamat coding!
Ketika pengecualian dilemparkan dan kontrol berpindah dari blok percobaan ke pengendali, waktu berjalan C ++ memanggil destruktor untuk semua objek otomatis yang dibangun sejak awal blok percobaan. Proses ini disebut stack unwinding. Objek otomatis dihancurkan dalam urutan terbalik dari konstruksi mereka. (Objek otomatis adalah objek lokal yang telah dinyatakan otomatis atau mendaftar, atau tidak dinyatakan statis atau eksternal. Objek x otomatis dihapus setiap kali program keluar dari blok di mana x dinyatakan.)
Jika pengecualian dilemparkan selama konstruksi objek yang terdiri dari sub-objek atau elemen array, destruktor hanya dipanggil untuk sub-objek atau elemen array yang berhasil dibangun sebelum pengecualian dilemparkan. Destruktor untuk objek statis lokal hanya akan dipanggil jika objek berhasil dibuat.
Dalam Java stack tanpa disadari atau tidak, tidak terlalu penting (dengan pengumpul sampah). Dalam banyak makalah penanganan pengecualian saya melihat konsep ini (tumpukan unwinding), dalam khusus mereka berurusan dengan penanganan pengecualian C atau C ++. dengan try catch
blok kita tidak akan lupa: tumpukan gratis dari semua objek setelah blok lokal .