Библиотека программиста

«Если отладка - процесс удаления ошибок, то программирование должно быть процессом их внесения»

Э.Дейкстра

Главная страница > Язык Object Pascal > 32. Примеры обработки исключительных ситуаций

32. Примеры обработки исключительных ситуаций

Ниже приведены процедуры A, B и C, обсуждавшиеся ранее, воплощенные в новом синтаксисе Object Pascal:

{$APPTYPE CONSOLE}

Program Excep_1; {Иллюстрация обработки исключительных ситуаций.

Программа построена на основе заимствованного примера

с добавлением изменений.

Автор Овсянник В.Н. }

uses SysUtils;

type ESampleError = class(Exception);

var

ErrorCondition: Boolean;

i : byte;

procedure C;

begin

writeln('Enter C');

write('Input byte value>');

ReadLn(i);

i:=20 div i; {Здесь возникнет ИС при вводе i=0}

// любые другие операторы

if ErrorCondition then

begin {Создаем самостоятельно ИС}

writeln('Raising exception in C');

raise ESampleError.Create('Error in procedure C!');

end;

writeln('Exit C');

end;

procedure B;

begin

writeln('enter B');

C;

writeln('exit B');

end;

procedure A;

begin

writeln('Enter A');

try

writeln('Enter A''s try block');

B;

writeln('After B call');

except

// а если добавить какой-нибудь оператор сюда, то когда он будет выполнен?

on ESampleError do

writeln('Inside A''s ESampleError handler');

on EIntError do

writeln('Inside A''s EIntError handler');

end;

writeln('Exit A');

end;

begin

writeln('begin main');

ErrorCondition := false;

A;

writeln('end main');

ReadLn;

end.

При ErrorCondition = True и вводе ненулевого значения i программа выдаст:

begin main

Enter A

Enter A's try block

enter B

Enter C

Input byte value>12

Raising exception in C

Inside A's ESampleError handler

Exit A

end main

При ErrorCondition = True и вводе нулевого значения i программа выдаст:

begin main

Enter A

Enter A's try block

enter B

Enter C

Input byte value>0

Raising exception in C

Inside A's EIntError handler

Exit A

end main

Процедура C проверяет наличие ошибки (в примере это значение глобальной переменной) и, если она есть, C вызывает (raise) исключительную ситуацию (ИС) класса ESampleError, т.е. создает экземпляр этого класса и передает ему необходимую информацию. При возникновении ИС или ее создании с помощью raise в момент выполнения программы будет выведено окно с сообщением об ошибке, но работа приложения не будет завершена. Для того чтобы избежать появления окна, надо (в Delphi5) в меню "Tools/Debugger Options/Language Exceptions" снять флажок "Stop on Delphi exeptions".

Процедура A помещает часть кода в блок try ... except. Первая часть этого блока содержит часть кода, аналогично конструкции begin … end. Эта часть кода завершается зарезервированным словом except, далее следует один или более обработчиков исключительных ситуаций on x do y, далее может быть включен необязательный блок else, вся конструкция заканчивается end. В конструкции, назначающей определенную обработку для конкретной исключительной ситуации (on x do y), после зарезервированного слова on указывается класс исключительной ситуации, а после do следует собственно код обработки данной ошибки. Код обработки ошибочной ситуации может быть одиночным оператором или составным. Если возникшая ИС подходит по типу к указанному после on, то выполнение программы переходит сюда (на код после do). Исключительная ситуация подходит в том случае, если она того же класса, что указан в on, либо является его потомком. Например, в случае on EFileNotFound обрабатываться будет ситуация, когда файл не найден. А в случае on EFileIO - все ошибки при работе с файлами, в том числе и предыдущая ситуация. В блоке else обрабатываются все ошибки, не обработанные до этого.

Приведенные в примере процедуры содержат код (строка с writeln), который отображает путь выполнения программы. Когда C вызывает exception, программа сразу переходит на обработчик ошибок в процедуре A, игнорируя оставшуюся часть кода в процедурах B и C.

После того, как найден подходящий обработчик ошибки, поиск заканчивается. После выполнения кода обработчика, программа продолжает выполняться с оператора, стоящего после слова end блока try ... except (в примере - writeln('Exit A')).

Если между словами except и end не указана ни одна ИС, т.е. нет консnрукций on … do, то приведенный здесь код обрабатывает все ИС:

try

. . .

except

UniversalExceptionHandler;

end;

Если же указан хотя бы один оператор on, то между try и except не должно быть никаких других операторов, что поясняет следующий фрагмент кода:

try

. . .

except

// оператор_1– ошибка

on EIntErr do

begin

// реакция на ИС при выполнении целочисленных операций

end;

// оператор_2– ошибка

end;

Конструкция try … except подходит, если известно, какой тип ошибок нужно обрабатывать в конкретной ситуации. Но что делать, если требуется выполнить некоторые действия в любом случае, произошла ошибка или нет? Это тот случай, когда понадобится конструкция try … finally.

Рассмотрим модифицированную процедуру B:

procedure NewB;

var

P: Pointer;

begin

writeln('enter B');

GetMem(P, 1000);

C;

FreeMem(P, 1000);

writeln('exit B');

end;

Если C вызывает ИС, то программа уже не возвращается в процедуру B. А что же с теми 1000 байтами памяти, захваченными в B? Строка FreeMem(P,1000) не выполнится и Вы потеряете кусок памяти. Как это исправить? Нужно ненавязчиво включить процедуру B в процесс, например:

procedure NewB;

var

P: Pointer;

begin

writeln('enter NewB');

GetMem(P, 1000);

try

writeln('enter NewB''s try block');

C;

writeln('end of NewB''s try block');

finally

writeln('inside NewB''s finally block');

FreeMem(P, 1000);

end;

writeln('exit NewB');

end;

Если в A поместить вызов NewB вместо B, то программа выведет сообщения следующим образом:

begin main

Enter A

Enter A's try block

enter NewB

enter NewB's try block

Enter C

Raising exception in C

inside NewB's finally block

Inside A's ESampleError handler

Exit A

end main

Код в блоке finally выполнится при любой ошибке, возникшей в соответствующем блоке try. Он же выполнится и в том случае, если ошибки не возникло. В любом случае память будет освобождена. Если возникла ошибка, то сначала выполняется блок finally, затем начинается поиск подходящего обработчика. В штатной ситуации после блока finally программа переходит на следующее предложение после блока.

Почему вызов GetMem не помещен внутрь блока try? Этот вызов может окончиться неудачно и вызвать ИС EOutOfMemory. Если это произошло, то FreeMem попытается освободить память, которая не была распределена. Когда мы размещаем GetMem вне защищаемого участка, то предполагаем, что B сможет получить нужное количество памяти, а если нет, то более верхняя процедура получит уведомление EOutOfMemory.

А что, если требуется в B распределить 4 области памяти по схеме "все или ничего"? Если первые две попытки удались, а третья провалилась, то как освободить захваченную область память? Можно так:

Procedure NewB;

var

p,q,r,s: Pointer;

Begin

P := nil;

Q := nil;

R := nil;

S := nil;

try

GetMem(P, 1000);

GetMem(Q, 1000);

GetMem(R, 1000);

GetMem(S, 1000);

C;

finally

if P <> nil then FreeMem(P, 1000);

if Q <> nil then FreeMem(Q, 1000);

if R <> nil then FreeMem(R, 1000);

if S <> nil then FreeMem(S, 1000);

end;

End;

Установив сначала указатели в nil можно определить, успешно ли прошел вызов GetMem.

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





Следующая статья >>
33. Дополнительные возможности обработки ИС
 
При использовании любых материалов с сайта http://www.introligator.org
обратная ссылка обязательна.
Rambler's Top100 Рейтинг@Mail.ru