Apa itu stack unwinding?

193

Apa itu stack unwinding? Cari tetapi tidak menemukan jawaban yang mencerahkan!

Rajendra Uppal
sumber
76
Jika dia tidak tahu apa itu, bagaimana Anda bisa berharap dia tahu mereka tidak sama untuk C dan untuk C ++?
dreamlax
@dreamlax: Jadi, bagaimana konsep "stack unwinding" berbeda dalam C & C ++?
Destructor
2
@PravasiMeet: C tidak memiliki penanganan perkecualian, jadi stack unwinding sangat sederhana, namun, dalam C ++, jika pengecualian dilempar atau fungsi keluar, stack unwinding melibatkan penghancuran objek C ++ dengan durasi penyimpanan otomatis.
dreamlax

Jawaban:

150

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 pleakakan hilang jika pengecualian dilemparkan, sedangkan memori yang dialokasikan untuk sakan dirilis dengan benar oleh std::stringdestruktor 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 .

Nikolai Fetissov
sumber
Itu benar-benar mencerahkan! Jadi saya mendapatkan ini: jika proses saya terhenti secara tiba-tiba selama meninggalkan blok APA SAJA di mana tumpukan waktu sedang muncul maka mungkin terjadi bahwa kode setelah kode handler pengecualian, tidak akan dieksekusi sama sekali, dan dapat menyebabkan kebocoran memori, tumpukan korupsi dll.
Rajendra Uppal
15
Jika program "crash" (mis. Diakhiri karena kesalahan), maka setiap kebocoran memori atau tumpukan korupsi tidak relevan karena memori dilepaskan pada saat penghentian.
Tyler McHenry
1
Persis. Terima kasih. Saya hanya menjadi sedikit disleksia hari ini.
Nikolai Fetissov
11
@TylerMcHenry: Standar tidak menjamin bahwa sumber daya atau memori dilepaskan pada saat penghentian. Kebanyakan OS kebetulan melakukannya.
Mooing Duck
3
delete [] pleak;hanya tercapai jika x == 0.
Jib
71

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:

  1. 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 .

  2. 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).

utnapistim
sumber
2
Di sisi lain. Sementara goto tidak boleh disalahgunakan, mereka memang menyebabkan tumpukan unwinding di MSVC (bukan di GCC, jadi mungkin ekstensi). setjmp dan longjmp melakukan ini secara lintas platform, dengan fleksibilitas yang agak kurang.
Patrick Niedzielski
10
Saya baru saja menguji ini dengan gcc dan tidak benar memanggil destruktor ketika Anda keluar dari blok kode. Lihat stackoverflow.com/questions/334780/… - sebagaimana disebutkan dalam tautan itu, ini juga merupakan bagian dari standar.
Damyan
1
membaca jawaban Nikolai, jrista, dan jawaban Anda dalam urutan ini, sekarang masuk akal!
n611x007
@sashoalm Apakah Anda benar-benar berpikir perlu mengedit posting tujuh tahun kemudian?
David Hoelzer
41

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.

jrista
sumber
4
Tidak ada yang istimewa tentang trybalok. Stack objek yang dialokasikan di blok mana pun (apakah tryatau tidak) dapat dibatalkan saat blok keluar.
Chris Jester-Young
Sudah lama sejak saya telah melakukan banyak coding C ++. Saya harus menggali jawaban itu dari kedalaman yang berkarat. ; P
jrista
jangan khawatir. Setiap orang kadang-kadang memiliki "keburukan" mereka.
bitc
13

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"
Chris Jester-Young
sumber
Apakah ini berlaku untuk semua blok? Maksud saya jika hanya ada {// beberapa objek lokal}
Rajendra Uppal
@Rajendra: Ya, blok anonim menentukan area cakupan, jadi itu juga penting.
Michael Myers
12

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]

John Weldon
sumber
9

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:

  1. Sebagai mekanisme aliran kontrol runtime (pengecualian C ++, C longjmp (), dll).
  2. Dalam debugger, untuk menampilkan tumpukan kepada pengguna.
  3. Dalam profiler, untuk mengambil sampel tumpukan.
  4. 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 .

L. Langó
sumber
7

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.

bbv
sumber
5
Tindakan debugger biasanya disebut "Stack Walking" yang hanya mengurai tumpukan. "Stack Unwinding" menyiratkan tidak hanya "Stack Walking" tetapi juga menyebut penghancur objek yang ada di stack.
Adisak
@Adisak Saya tidak tahu itu juga disebut "tumpukan berjalan". Saya selalu melihat "stack unwinding" dalam konteks semua artikel debugger dan bahkan di dalam kode gdb. Saya merasa "stack unwinding" lebih tepat karena ini bukan hanya tentang mengintip informasi stack untuk setiap fungsi, tetapi juga melibatkan unwinding informasi frame (cf CFI in dwarf). Ini diproses secara berurutan satu fungsi satu per satu.
bbv
Saya kira "tumpukan berjalan" dibuat lebih terkenal oleh Windows. Juga, saya menemukan sebagai contoh code.google.com/p/google-breakpad/wiki/StackWalking terlepas dari dokumen standar kerdil itu sendiri menggunakan istilah unwinding beberapa kali. Meskipun setuju, itu adalah pelonggaran virtual. Selain itu, pertanyaannya tampaknya menanyakan setiap arti yang mungkin "stack uninding" dapat sarankan.
bbv
7

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):

masukkan deskripsi gambar di sini

Di dalam pic:

  • Yang teratas adalah eksekusi panggilan biasa (tanpa kecuali).
  • Bawah ketika pengecualian dilemparkan.

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-catchblok, tetapi tidak sebelum menghapus semua entri sebelum dari tumpukan panggilan fungsi.

Saurav Sahu
sumber
Diagram bagus tapi penjelasannya agak membingungkan, yaitu. ... dengan melampirkan blok coba-tangkap, tetapi tidak sebelum menghapus semua entri sebelum dari tumpukan panggilan fungsi ...
Atul
3

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!

DigitalEye
sumber
2

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.

MK.
sumber
Anda harus memberikan tautan ke artikel asli tempat Anda menyalin jawaban ini dari: Basis Pengetahuan IBM - Stack Unwinding
w128
0

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 catchblok kita tidak akan lupa: tumpukan gratis dari semua objek setelah blok lokal .

pengguna2568330
sumber