Wyjątki i odwijanie stosu w języku C++Exceptions and Stack Unwinding in C++

W mechanizmie wyjątków C++, kontrola zostaje przekazana z instrukcji throw do pierwszej instrukcji catch, która może obsłużyć wyrzucony typ.In the C++ exception mechanism, control moves from the throw statement to the first catch statement that can handle the thrown type. Po osiągnięciu instrukcji catch wszystkie zmienne automatyczne, które są w zakresie między instrukcjami throw i catch, są niszczone w procesie, który jest znany jako odwracanie stosu.When the catch statement is reached, all of the automatic variables that are in scope between the throw and catch statements are destroyed in a process that is known as stack unwinding. W odwijaniu stosu, wykonanie przebiega w następujący sposób:In stack unwinding, execution proceeds as follows:

  1. Formant osiąga try instrukcję przez normalne wykonywanie sekwencyjne.Control reaches the try statement by normal sequential execution. Sekcja chroniona w try bloku jest wykonywana.The guarded section in the try block is executed.

  2. Jeśli podczas wykonywania sekcji chronionej nie wystąpi wyjątek, catch klauzule znajdujące się po try bloku nie są wykonywane.If no exception is thrown during execution of the guarded section, the catch clauses that follow the try block are not executed. Wykonywanie jest kontynuowane w instrukcji po ostatniej catch klauzuli, która następuje po elemencie skojarzonym try bloku.Execution continues at the statement after the last catch clause that follows the associated try block.

  3. Jeśli wyjątek jest zgłaszany podczas wykonywania sekcji chronionej lub procedury, którą Sekcja chroniona wywołuje bezpośrednio lub pośrednio, obiekt wyjątku jest tworzony na podstawie obiektu utworzonego przez throw operand.If an exception is thrown during execution of the guarded section or in any routine that the guarded section calls either directly or indirectly, an exception object is created from the object that is created by the throw operand. (Oznacza to, że może być używany Konstruktor kopiujący). W tym momencie kompilator szuka catch klauzuli w wyższym kontekście wykonywania, który może obsłużyć wyjątek zgłoszonego typu lub dla catch programu obsługi, który może obsługiwać dowolny typ wyjątku.(This implies that a copy constructor may be involved.) At this point, the compiler looks for a catch clause in a higher execution context that can handle an exception of the type that is thrown, or for a catch handler that can handle any type of exception. catch Programy obsługi są badane w kolejności ich występowania po try bloku.The catch handlers are examined in order of their appearance after the try block. Jeśli nie zostanie znaleziona odpowiednia procedura obsługi, try zostanie zbadany następny dynamicznie otaczający blok.If no appropriate handler is found, the next dynamically enclosing try block is examined. Ten proces jest kontynuowany do momentu zbadania najbardziej zewnętrznego otaczającego try bloku.This process continues until the outermost enclosing try block is examined.

  4. Jeśli nadal nie znaleziono pasującej klauzuli obsługi lub jeśli podczas procesu odwijania wystąpi wyjątek, ale zanim klauzula obsługi przejmie kontrolę, wywoływana jest wstępnie zdefiniowana funkcja terminate.If a matching handler is still not found, or if an exception occurs during the unwinding process but before the handler gets control, the predefined run-time function terminate is called. Jeśli wyjątek wystąpi po wyrzuceniu wyjątku, ale przed rozpoczęciem odwijania, wywoływane jest terminate.If an exception occurs after the exception is thrown but before the unwind begins, terminate is called.

  5. W przypadku znalezienia zgodnego catch programu obsługi i przechwycenia przez wartość jego parametr formalny jest inicjowany przez skopiowanie obiektu Exception.If a matching catch handler is found, and it catches by value, its formal parameter is initialized by copying the exception object. Jeżeli przechwyci kontrolę przez odwołanie, inicjowany jest parametr odwołujący się do obiektu wyjątku.If it catches by reference, the parameter is initialized to refer to the exception object. Po zainicjowaniu parametru formalnego, rozpocznie się proces odwijania stosu.After the formal parameter is initialized, the process of unwinding the stack begins. Dotyczy to niszczenia wszystkich obiektów automatycznych, które zostały w pełni skonstruowane — ale nie zostały jeszcze zadestruktorne — między początkiem try bloku, który jest skojarzony z catch programem obsługi, a lokacją throw wyjątku.This involves the destruction of all automatic objects that were fully constructed—but not yet destructed—between the beginning of the try block that is associated with the catch handler and the throw site of the exception. Destrukcja następuje w odwrotnej kolejności do konstrukcji.Destruction occurs in reverse order of construction. catch Procedura obsługi jest wykonywana, a program wznawia wykonywanie po ostatniej obsłudze — to znaczy, na pierwszej instrukcji lub konstrukcji, która nie jest catch programem obsługi.The catch handler is executed and the program resumes execution after the last handler—that is, at the first statement or construct that is not a catch handler. Kontrolka może wprowadzać tylko catch procedurę obsługi poprzez zgłoszony wyjątek, nigdy za pomocą goto instrukcji lub case etykiety w switch instrukcji.Control can only enter a catch handler through a thrown exception, never through a goto statement or a case label in a switch statement.

Przykład odwinięcia stosuStack unwinding example

W poniższym przykładzie zilustrowano, jak stos jest odwijany, gdy zostaje wyrzucony wyjątek.The following example demonstrates how the stack is unwound when an exception is thrown. Wykonanie na wątku przechodzi z instrukcji throw w C do instrukcji catch w main i rozwija każdą funkcję po drodze.Execution on the thread jumps from the throw statement in C to the catch statement in main, and unwinds each function along the way. Należy zauważyć kolejność, w której obiekty Dummy są tworzone i następnie niszczone, gdy wykraczają poza zakres.Notice the order in which the Dummy objects are created and then destroyed as they go out of scope. Należy również zauważyć, że żadna funkcja nie zostaje zakończona z wyjątkiem main, która zawiera instrukcję catch.Also notice that no function completes except main, which contains the catch statement. Funkcja A nigdy nie wraca z wywołania B(), a B nigdy nie wraca z wywołania C().Function A never returns from its call to B(), and B never returns from its call to C(). Jeśli usuniesz komentarz definicji wskaźnika Dummy i odpowiadającą instrukcję delete, a następnie uruchomisz program, zobaczysz, że wskaźnik nigdy nie jest usuwany.If you uncomment the definition of the Dummy pointer and the corresponding delete statement, and then run the program, notice that the pointer is never deleted. Pokazuje to, co może się zdarzyć, gdy funkcje nie zapewniają gwarancji wyjątku.This shows what can happen when functions do not provide an exception guarantee. Aby uzyskać więcej informacji, zobacz: Jak projektować obsługę wyjątków.For more information, see How to: Design for Exceptions. Jeśli komentarz wystąpi poza instrukcją catch, można zaobserwować, co się dzieje, gdy program zakończy wykonanie z powodu nieobsługiwanego wyjątku.If you comment out the catch statement, you can observe what happens when a program terminates because of an unhandled exception.

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

*/