Bagikan melalui


Pengecualian dan Stack Unwinding di C++

Dalam mekanisme pengecualian C++, kontrol berpindah dari pernyataan lemparan ke pernyataan tangkapan pertama yang dapat menangani jenis yang dilemparkan. Ketika pernyataan tangkapan tercapai, semua variabel otomatis yang berada dalam cakupan antara pernyataan lemparan dan tangkapan dihancurkan dalam proses yang dikenal sebagai stack unwinding. Dalam unwinding tumpukan, eksekusi berlanjut sebagai berikut:

  1. Kontrol mencapai try pernyataan berdasarkan eksekusi berurutan normal. Bagian yang dijaga di try blok dijalankan.

  2. Jika tidak ada pengecualian yang dilemparkan selama eksekusi bagian yang dijaga, catch klausul yang mengikuti try blok tidak dijalankan. Eksekusi berlanjut pada pernyataan setelah klausul terakhir catch yang mengikuti blok terkait try .

  3. Jika pengecualian dilemparkan selama eksekusi bagian yang dijaga atau dalam rutinitas apa pun yang dipanggil bagian yang dijaga baik secara langsung atau tidak langsung, objek pengecualian dibuat dari objek yang dibuat oleh throw operand. (Ini menyiratkan bahwa konstruktor salinan mungkin terlibat.) Pada titik ini, pengkompilasi mencari catch klausul dalam konteks eksekusi yang lebih tinggi yang dapat menangani pengecualian jenis yang dilemparkan, atau untuk catch handler yang dapat menangani semua jenis pengecualian. Handler catch diperiksa dalam urutan penampilan mereka setelah try blok. Jika tidak ada handler yang sesuai yang ditemukan, blok penutup dinamis try berikutnya akan diperiksa. Proses ini berlanjut sampai blok penutup terluar try diperiksa.

  4. Jika handler yang cocok masih belum ditemukan, atau jika terjadi pengecualian selama proses unwinding tetapi sebelum handler mendapatkan kontrol, fungsi terminate run-time yang telah ditentukan sebelumnya dipanggil. Jika pengecualian terjadi setelah pengecualian dilemparkan tetapi sebelum unwind dimulai, terminate dipanggil.

  5. Jika handler yang catch cocok ditemukan, dan menangkap berdasarkan nilai, parameter formalnya diinisialisasi dengan menyalin objek pengecualian. Jika menangkap berdasarkan referensi, parameter diinisialisasi untuk merujuk ke objek pengecualian. Setelah parameter formal diinisialisasi, proses melepas tumpukan dimulai. Ini melibatkan penghancuran semua objek otomatis yang sepenuhnya dibangun—tetapi belum dihancurkan—antara awal try blok yang terkait dengan catch handler dan situs lemparan pengecualian. Penghancuran terjadi dalam urutan terbalik konstruksi. Handler catch dijalankan dan program melanjutkan eksekusi setelah handler terakhir—yaitu, pada pernyataan pertama atau konstruksi yang bukan handler catch . Kontrol hanya dapat memasukkan catch handler melalui pengecualian yang dilemparkan, tidak pernah melalui goto pernyataan atau case label dalam switch pernyataan.

Contoh unwinding tumpukan

Contoh berikut menunjukkan bagaimana tumpukan dilepas saat pengecualian dilemparkan. Eksekusi pada utas melompat dari pernyataan C lemparan ke pernyataan tangkapan di main, dan melepas lelah setiap fungsi di sepanjang jalan. Perhatikan urutan Dummy pembuatan objek dan kemudian dihancurkan saat keluar dari cakupan. Perhatikan juga bahwa tidak ada fungsi yang selesai kecuali main, yang berisi pernyataan tangkapan. Fungsi A tidak pernah kembali dari panggilannya ke B(), dan B tidak pernah kembali dari panggilannya ke C(). Jika Anda membatalkan komentar definisi Dummy pointer dan pernyataan penghapusan yang sesuai, lalu menjalankan program, perhatikan bahwa pointer tidak pernah dihapus. Ini menunjukkan apa yang dapat terjadi ketika fungsi tidak memberikan jaminan pengecualian. Untuk informasi selengkapnya, lihat Cara: Desain untuk Pengecualian. Jika Anda mengomentari pernyataan tangkapan, Anda dapat mengamati apa yang terjadi ketika program berakhir karena pengecualian yang tidak tertangani.

#include <string>
#include <iostream>
using namespace std;

class MyException{};
class Dummy
{
    public:
    Dummy(string s) : MyName(s) { PrintMsg("Created Dummy:"); }
    Dummy(const Dummy& other) : MyName(other.MyName){ PrintMsg("Copy created Dummy:"); }
    ~Dummy(){ PrintMsg("Destroyed Dummy:"); }
    void PrintMsg(string s) { cout << s  << MyName <<  endl; }
    string MyName;
    int level;
};

void C(Dummy d, int i)
{
    cout << "Entering FunctionC" << endl;
    d.MyName = " C";
    throw MyException();

    cout << "Exiting FunctionC" << endl;
}

void B(Dummy d, int i)
{
    cout << "Entering FunctionB" << endl;
    d.MyName = "B";
    C(d, i + 1);
    cout << "Exiting FunctionB" << endl;
}

void A(Dummy d, int i)
{
    cout << "Entering FunctionA" << endl;
    d.MyName = " A" ;
  //  Dummy* pd = new Dummy("new Dummy"); //Not exception safe!!!
    B(d, i + 1);
 //   delete pd;
    cout << "Exiting FunctionA" << endl;
}

int main()
{
    cout << "Entering main" << endl;
    try
    {
        Dummy d(" M");
        A(d,1);
    }
    catch (MyException& e)
    {
        cout << "Caught an exception of type: " << typeid(e).name() << endl;
    }

    cout << "Exiting main." << endl;
    char c;
    cin >> c;
}

/* Output:
    Entering main
    Created Dummy: M
    Copy created Dummy: M
    Entering FunctionA
    Copy created Dummy: A
    Entering FunctionB
    Copy created Dummy: B
    Entering FunctionC
    Destroyed Dummy: C
    Destroyed Dummy: B
    Destroyed Dummy: A
    Destroyed Dummy: M
    Caught an exception of type: class MyException
    Exiting main.

*/