Исключения и стек
Когда выбрасывается исключение, управление передается некоторому обработчику; он должен принадлежать функции, еще находящейся на стеке вызовов программы (см. в главе 5 об окне стека вызовов). Но эта функция не обязательно будет текущей функцией (выбросившей исключение). Например, пробный блок и обработчики находятся в одной функции, а исключение выбрасывается некоторой функцией, вызываемой из пробного ; блока; между ними может тянуться целая цепочка вложенных вызовов.
В результате некоторой последовательности вызовов на стеке (физическом) будут находиться адреса возврата, передаваемые аргументы и локальные переменные вызванных к данному моменту функций, а также вспомогательная информация (формирующая кадры стека), которая используется кодом входа и выхода из функции.
При нормальном возврате из функции она удаляет со стека локальные переменные, вызывая соответствующий деструктор, если переменная — представитель класса. Затем она восстанавливает кадр стека вызывающей функции и исполняет инструкцию возврата. После этого вызывающая функция удаляет со стека передававшиеся аргументы, также вызывая при необходимости деструкторы.
Приведем небольшую иллюстрацию. Ниже показана программа, состоящая из main () и двух функций FuncA () и FuncB () . Главная функция создает объект класса S и передает его FuncA (), которая его модифицирует и передает в FuncB (). Затем управление возвращается к main () .
Листинг 12.2. Работа стека при вызовах функций
///////////////////////////////////////////////////
// Stack.срp: Работа стека.
//
#include <iostream.h>
#pragma hdrstop
#include <condefs.h>
struct S // Простой класс. {
int s;
S(int ss): s(ss) // Конструктор (преобразования из int).
{
cout << "Constructor for "<< s << endl;
} S (const S& src) // Конструктор копии.
{
s = src.s;
cout << "Copy constructor for " << s << endl;
}
~S() // Деструктор.
{
cout << "Destructor of " << s << endl;
} };
void FuncB(S obj)
{
cout << "In FuncB: got << obj.s endl;
cout << "Exiting FuncB..." << endl;
}
void FuncA(S obj)
{
cout << "In FuncA: got"<< obj.s << endl;
obj.s = 22; // Модифицирует полученную копию объекта и...
FuncB(obj); // ...передает ее FuncB().
cout << "Exiting FuncA..." << end1;
}
int main() {
S mainObj = 11; // Локальный объект.
cout << "In main..." << endl; FuncA(mainObj);
cout << "Exiting main..." << endl;
return 0;
}
Программа выводит следующие сообщения:
Constructor for 11
In main...
Copy constructor for 11
In FuncA: got 11
Copy constructor for 22
In FuncB: got 22
Exiting FuncB...
Destructor of 22
Exiting FuncA...
Destructor of 22
Exiting main...
Destructor of 11
Здесь видно, как создается копия объекта при передачи параметра (по значению) и как она удаляется при возврате из функции.
При появлении исключения нормального возврата не происходит. Требуемый обработчик может находиться на несколько позиций выше по стеку вызовов. Запускается механизм разматывания стека, удаляющий находящиеся на нем локальные объекты с вызовом деструкторов, как если бы происходил нормальный возврат из функций, находящихся еще на стеке вызовов.
Можно слегка модифицировать предыдущий пример, организовав пробный блок в main() и заставив FuncB() выбрасывать исключение в виде строки:
…
void FuncB(S obj)
{
cout << "In FuncB: got " << obj.s << endl;
cout << "Throwing exception..." << endl;
throw "Exception!";
cout << "Exiting FuncB..." << endl;
}
int main() {
S mainObj = 11; // Локальный объект.
cout << "In main..." << endl;
try {
FuncA(mainObj);
} catch(char* str) {
cout << "Gaught in main: " << str << end1;
} cout << "Exiting main..." << endl;
return 0;
}
Теперь программа выводит:
Constructor for 11
In main...
Copy constructor for 11
In FuncA: got 11
Copy constructor for 22
In FuncB: got 22
Throwing exception...
Destructor of 22
Destructor of 22
Caught in main: Exception!
Exiting main...
Destructor of 11
Временные копии объекта уничтожаются по-прежнему, хотя возврата из функции в обычном смысле слова не происходит.