Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Структурная обработка исключений (SEH) в примерах. Часть 1


Домашняя страница www.devdoc.ru

DevDoc - это новые статьи по программированию каждую неделю.

Заходи и читай!

Домашняя страница Письмо автору Архив рассылки Публикация статьи

Выпуск №19

Здравствуйте уважаемые подписчики, сегодня в номере:

  • От автора
  • Объявление
  • Статья "Структурная обработка исключений (SEH) в примерах. Часть 1"

От автора

В сегодняшнем выпуске мы продолжаем говорить про исключения. Это очень обширная тема и очень трудно уместить много информации в одном выпуске. Для тех, кто только подписался на рассылку, я рекомендую посмотреть предыдущий выпуск в архиве.

Исключения вносят некоторую путаницу в порядок выполнения программы. При работе с исключениями порядок выполнения кода может быть очень запутанным, но вполне предсказуемым. В статье на примерах показаны все хитросплетения работы системы SEH.

Обявление

Я ухожу в отпуск и большую часть времени я проведу вдали от компьютера. Поэтому рассылка в течение ближайшего месяца будет выходить нерегулярно, но будет! За месяц будет 4 выпуска как обычно. Нарушиться только график выпусков. Через месяц все вернется на круги свои, и рассылка опять будет выходить вечером в понедельник. Надеюсь на ваше терпение и понимание.


Постоянная ссылка на статью (с картинками): http://www.devdoc.ru/index.php/content/view/seh_example1.htm

Автор: Кудинов Александр
Последняя модификация: 2007-04-23 23:44:44

Структурная обработка исключений (SEH) в примерах. Часть 1

Введение

Эта статья продолжает цикл о структурной обработке исключений. Синтаксис и общие правила работы с исключениями описаны в первой статье: «Введение в обработку структурированных исключений SEH». Этого не достаточно для грамотного проектирования. Статья носит скорее ознакомительный характер.

Настоящий материал построен на примерах, чтобы на практике продемонстрировать, как работают исключения операционной системы. Все примеры носят синтетический характер и служат исключительно для демонстрации возможностей.

Обработчики завершения

Существует множество применений обработчиков завершения. Например, их очень удобно использовать для управления ресурсами и устранения утечек.

void foo()
{
 HANDLE hFile = INVALID_HANDLE_VALUE;
 __try
 {
  hFile = CreateFile(.....);
  if(INVALID_HANDLE_VALUE == hFile)
   return;
  char *a = new char[100];
  if(!fillbuffer(a))
   return;
 }
 __finally
 {
  if(INVALID_HANDLE_VALUE != hFile)
   CloseHandle(hFile);
 }
}

Блок __finaly будет выполнен в любом случае, даже если блок __try успешно завершиться. Гораздо интересней ситуация, когда происходит отклонение от основного потока выполнения. Если функция fillbuffer() вернет ошибку – произойдет выход из функции foo(). Но перед этим все равно выполниться блок __finaly. Как видите, это позволяет очень элегантно освобождать все занятые ресурсы.

Для уменьшения накладных расходов при обработке выходов из блока __try c помощью операторов условных/безусловных переходов, надо использовать команду _leave.

В данном примере это не столь актуально, т.к. код очень короткий. Преимущества становятся очевидны, если представить, что код на пару порядков сложнее и больше. Во время модификации кода можно легко забыть об освобождении ресурсов. При структурированной обработке исключений об этом можно не волноваться. Алгоритм освобождения ресурсов будет поддерживаться компилятором в адекватном состоянии.

Такого же эффекта можно достичь, используя функциональные замыкания. Для этого делают объект-обертку над ресурсом. В конструкторе ресурс выделяется, а в деструкторе освобождается. Подход замечательно работает в нормально работающей программе. Если же происходит аппаратное исключение, поток может аварийно завершиться, и деструкторы не будут вызваны.
Дальше, как говориться, возможны варианты. Предположим, что программа состоит из одного потока. Тогда программа автоматически завершиться. Считается, что ОС освобождает все ресурсы, которые выделила программа. Казалось бы, что в этом случае не будет никаких проблем, кроме краха программы. Это не совсем так. Если программа работает с именованными объектами синхронизации, - могут быть проблемы. Программа, может захватить объект, а потом завершиться. Объект будет оставаться захваченным и может заблокировать работу других процессов.

Структурная обработка исключений может помочь в описанной ситуации. ОС гарантирует, что блок __finaly будет выполнен при любых неприятностях в программе. Далее будет рассмотрен пример, показывающий всю мощь структурной обработки.

Обратите внимание, что блок __finaly не будет выполняться, если вызывать API функции для завершения потока или программы: TerminateThread, TerminateProcess и т.п.

Старайтесь не использовать обработчики исключения для написания «основного» потока выполнения. Это затрудняет понимание алгоритма и может давать существенные накладные затраты времени при локальной раскрутке.

Блок __finaly может также выполняться при глобальной раскрутке. Например, если исключительная ситуация возникла при вызове функции из защищенного кода. Т.о. выполнение __finaly может происходить в следующих случаях:

  • Локальная раскрутка – преждевременный выход из блока с помощью операторов перехода.
  • Нормальный переход от блока __try к __finaly.
  • Глобальная раскрутка.

Чтобы узнать причину, можно вызвать функцию:

BOOL AbnormalTermination();

Ее можно вызвать только из блока __finaly. Функция возвращает FALSE, если был нормальный переход к блоку. В случае глобальной или локальной раскрутке функция возвращает TRUE.

На заметку: Старайтесь избегать написания кода, который приводит к локальной раскрутке.

Обработчики исключений

Очевидно, что простого освобождения ресурсов не достаточно для создания надежных программ. Задача программиста так написать код, чтобы программа не падала даже в нештатных ситуациях. Существует стратегия, когда перехватываются все необработанные исключения, отображается красивый диалог, формируется отчет об ошибке для службы поддержки и… программа завершается. Это подробно описано в статье про MiniDumps. Для очень простых программ этого может быть достаточно. Для серьезных систем требуется большая надежность. В идеале надо обрабатывать все аппаратные и программные исключения. Рассмотрим пример, в котором иллюстрируются тонкости глобальной раскрутки.

void main() 
{ 
 
 // 1
 
 __try 
 { 
 
  // 2
  nested(); 
 
  // -- выполнение сюда не дойдет
 } 
 
 __except (/* 6 */ EXCEPTION_EXECUTE_HANDLER)
 { 
 
  // 8
 
 } 
 
 // 9 
 
} 
 
void nested()
{
 
 HANDLE hFile;
 // 3
 __try 
 { 
 
  // 4
  hFile = CreateFile(....);
 
  // 5
 
  *((int*)0) = 10;   //пишем по нулевому указателю и делаем исключение
 
 } 
 
 __finally 
 { 
 
  // 7.
 
  CloseHandle(hFile);
 } 
 
 // -- выполнение сюда не дойдет
}

В этом примере цифрами в комментариях показана последовательность выполнения программы. Рассмотрим, эти шаги по порядку:

  1. Начинаем выполнение программы.
  2. Вызываем функцию, которая сделает исключение
  3. Функция nested начинает свою работу и выполняет какие-либо действия
  4. Распределяется ресурс – открываем файл и получаем дескриптор.
  5. Здесь ошибка в программе, которая вызывает исключение.
  6. После возникновения исключения, система будет искать ближайший по стеку блок __except. Он находится в функции main. После того, как блок найден будет вызван фильтр исключений (выражение в скобках). В нашем примере – это константа EXCEPTION_EXECUTE_HANDLER. Поэтому будет выполнена глобальная раскрутка блоков __try/__finaly.
  7. Выполняется самый нижний блок __finaly. Освобождаются все ресурсы. Потом ищется вышестоящий блок __finaly и т.п. В нашем примере других блоков нет.
  8. Выполняется обработка исключения
  9. Программа продолжает свое выполнение

Особый интерес вызывает п.6. Как видим, при возникновении исключения, в первую очередь выполнятся функция-фильтр. От ее работы будет зависеть дальнейший способ обработки исключения.

Перепишем функцию nested():

void nested()
{
 
 HANDLE hFile;
 // 3
 __try 
 { 
 
  // 4
  hFile = CreateFile(....);
 
  // 5
 
  *((int*)0) = 10;   //пишем по нулевому указателю и делаем исключение
 
 } 
 
 __finally 
 { 
 
  // 7.
 
  CloseHandle(hFile);
  return;
 } 
 
 // -- выполнение сюда не дойдет
}

В блоке __finaly добавился оператор return. Т.о. произойдет штатный выход из функции nested и глобальная раскрутка остановиться в этой точке. Функция main продолжит выполнение, как будто не было никакого исключения.

Продолжение следует….


Если вам нравиться эта рассылка рекомендуйте ее своим друзьям. Подписаться можно по адресу http://subscribe.ru/catalog/comp.soft.prog.devdoc

Copyright (C) Kudinov Alexander, 2006-2007

Перепечатка и использование материалов запрещена без писменного разрешения автора.


В избранное