|
|
От: |
LaptevVV
|
|
| Дата: | 25.09.06 06:16 | ||
| Оценка: | 1 (1) | ||
Структурная обработка исключений (SEH) реализована и в Visual C++.NET 2003 и в C++ Builder 6 как расширение стандартного С++. Поддержка SEH обеспечивается операционной системой Windows, поэтому программы, в которых используется SEH, непереносимы. Кроме того, при структурной обработке исключений не выполняется вызов деструкторов — это наиболее важное отличие SEH от стандартного механизма. Поэтому Microsoft не рекомендует смешивать стандартные и структурные исключения в одной программе.
Структурная обработка исключений предоставляет две возможности: обработку исключений try/except и обработку завершения try/finally. Обработка завершения проще, поэтому начнем с нее. В системе Visual C++.NET 2003 синтаксис обработчика завершения выглядит так:
__try { } // защищенный блок __finally { } // блок завершения
Ключевые слова __try и __finally пишутся с двумя подчеркиваниями — обычно в системе Visual C++.NET 2003 так обозначаются все расширения стандартного С++.
Обработчик завершения гарантирует, что блок завершения будет выполнен при любой попытке выхода из защищенного блока — независимо от способа выхода. Выход из __try-блока осуществляется одним из следующих способов:
1. нормальное выполнение всех операторов блока от начала до конца;
2. возникновение исключения во время выполнения операторов блока;
3. выполнение одного из операторов перехода (break, continue, goto, return), хотя этого Джеффри Рихтер [49] рекомендует избегать.
Поведение программы после выполнения финального блока зависит от способа выхода из __try-блока. Если в финальном блоке нет никаких операторов перехода, и он выполняется до конца, то далее в соответствии со способом выхода из защищенного блока:
1. выполняются операторы после финального блока;
2. выполняются операторы после финального блока;
3. выполняется оператор перехода, который вызвал выход из защищенного блока.
Синтаксис обработчика исключений выглядит так:
// блок обработки исключения__try { } // защищенный блок __except (фильтр) { }
Обратите внимание, что в SEH после защищенного блока следует единственный обработчик, — либо обработчик завершения, либо обработчик исключения. Несколько обработчиков писать нельзя, и тем более нельзя писать несколько разных обработчиков. Однако разрешается конструкцию try/finally вкладывать в try/except и наоборот, и уровень вложенности не ограничен. Правда, в этом случае существует опасность «запутаться» в порядке выполнения блоков __except и __finally.
При нормальном ходе выполнения программы (когда исключения не возникает) после выполнения операторов защищенного блока блок-обработчик пропускается, и программа продолжает работу со следующего после него оператора. А вот если при выполнении операторов защищенного блока возникло исключение, то обработка его зависит от фильтра. Фильтр — это выражение (в том числе и вызов функции), которое должно принимать одно из трех возможных значений:
EXCEPTION_CONTINUE_EXECUTION (–1); EXCEPTION_CONTINUE_SEARCH (0); EXCEPTION_EXECUTE_HANDLER (1);
Очень часто эти константы прописываются в качестве фильтра непосредственно. Только последний вариант означает собственно обработку исключения: выполняются операторы блока обработки, после чего управление передается на первый оператор после него.
Если результат вычисления фильтра равен первой константе, то исключение отклоняется (exception is dismissed). Код в обработчике прерываний никогда не выполняется. Управление возвращается в точку возникновения прерывания и делается попытка снова выполнить ту же инструкцию, которая вызвала исключение.
На первый взгляд поведение не совсем логичное, ведь снова возникнет исключение. Однако не будем забывать, что фильтр – это выражение. Это означает, что на месте фильтра может быть прописано несколько выражений через запятую. В этих выражениях можно исправить причину, вызвавшую исключение. Если необходимо выполнить более сложную работу, то в качестве фильтра может быть прописан вызов функции. Эта функция может получить в качестве параметров любые переменные из защищенного блока, выполнить с ними нужную работу и возвратить константу EXCEPTION_CONTINUE_EXECUTION в качестве результата. Например, если произошло деление на ноль, то функция может изменить значение делителя, и программа сможет «хромать» дальше.
Фильтр EXCEPTION_CONTINUE_SEARCH по своему действию похож на оператор throw без аргумента, то есть «отправляет исключение дальше». Код в обработчике с таким фильтром не выполняется никогда.
Генерация программных исключений выполняется функцией Windows API RaisException(), которую мы рассматривать не будем — подробное изучение SEH в нашу задачу не входит. Однако на одном моменте нужно остановиться. Чем хорошо SEH, так это возможностью «отловить» аппаратные исключения и точно их идентифицировать, естественно, с помощью функции API
DWORD GetExceptCode();
Функция возвращает идентификатор типа исключения, которые определены в файле WinBase.h. Но подключать надо заголовок windows.h. В табл. 4.1 представлены некоторые из этих идентификаторов.
Таблица 4.1. Аппаратные исключения
Значение Описание
EXCEPTION_ACCESS_VIOLATION //Попытка обратиться к «чужой» памяти EXCEPTION_FLT_DIVIDE_BY_ZERO //Деление на ноль дробных чисел EXCEPTION_FLT_OVERFLOW //Переполнение для дробных чисел EXCEPTION_INT_DIVIDE_BY_ZERO //Деление на ноль для целых чисел EXCEPTION_INT_OVERFLOW //Переполнение для целых чисел EXCEPTION_STACK_OVERFLOW //Переполнение стека
В составе Windows API реализованы и другие функции, используемые в SEH. Останавливаться подробно мы на этом не будем, так как это не входит в нашу задачу. В справочной системе Visual C++.NET 2003 прописан пример, в котором модифицированы операторы ввода/вывода (листинг 4.22).
//Листинг 4.22. Структурная обработка исключений // exceptions_try_except_Statement.cpp // Пример try-except и try-finally #include <iostream> #include <windows.h> // для EXCEPTION_ACCESS_VIOLATION using std::cout; using std::endl; // функция-филтр int filter(unsigned int code, struct _EXCEPTION_POINTERS *ep) { cout<<"in filter."<< endl; if (code == EXCEPTION_ACCESS_VIOLATION) { cout<<"caught AV as expected."<<endl; return EXCEPTION_EXECUTE_HANDLER; // если авария } else { cout<<"didn't catch AV, unexpected."<< endl; return EXCEPTION_CONTINUE_SEARCH; // если нет аварии }; } int main() { int* p = 0x00000000; // специально-нулевой указатель cout<<"hello"<< endl; __try{ cout<<"in try 1"<< endl; __try{ cout<<"in try 2"<< endl; *p = 13; // access violation }__finally{ cout<<"in finally. termination: "<< endl; cout<<(AbnormalTermination() ? "\tabnormal" : "\tnormal")<< endl; } }__except(filter(GetExceptionCode(), GetExceptionInformation())){ cout<<"in except"<< endl; } cout<<"world"<< endl; }
Программа специально содержит ошибку (нулевой указатель), чтобы показать обработку возникающего исключения по нарушению доступа к памяти (access violation). Демонстрируется также обязательное выполнение финального блока, несмотря на исключение. Программа выводит на экран
hello in try 1 in try 2 in filter. caught AV as expected. in finally. termination: abnormal in except world
SEH очень подробно описана у Джеффри Рихтера.