Обработка ошибок паскаль

В процессе выполнения скрипта возможно возникновение исключительных ситуаций, ошибок. Если это происходит, то пользователь видит сообщение об ошибке «Опа!…». Вы можете перехватить ошибку и обработать ее особым образом. Для обработки исключительных ситуаций блок кода, в котором они возможны, заключается в составной оператор try.

try
  FS := TFileStream.Create(MyFileName, fmCreate);
except
  MsgBox(‘Ошибка’, ExceptionToString(ExceptionType, ExceptionParam);
end;

Если в блоке try происходит исключение, то выполнение кода прерывается и выполняется код в блоке except.
Есть другая конструкция – try..finally:

try
  FS := TFileStream.Create(MyFileName, fmCreate);
  tryfinally
    FS.Free;
  end;
except
  MsgBox(‘Ошибка’, ExceptionToString(ExceptionType, ExceptionParam);
end;

Код в блоке finally выполняется в любом случае, нормально ли отработал код или произошла ошибка. Если в блоке try используется ключевое слово exit (выход из процедуры/функции), то код в блоке finally также выполняется.
В операторе try могут быть использованы оба блока:

tryexceptfinallyend;

Вы можете программно сгенерировать исключение, используя процедуру RaiseException:

var
  filename, emsg: string; 
begin
  filename = '';
  try
    if filename = '' then
      RaiseException(erCustomError, 'Имя файла не может быть пустым');except
    emsg := ExceptionToString(ExceptionType, ExceptionParam);end;
end;

При обработке исключительных ситуаций могут быть использованы следующие процедуры и функции:

function ExceptionToString(Err: TIFException; Param: String): String

Формирует по типу исключения строку сообщения об ошибке.

function ExceptionType: TIFException

Возвращает тип возникшего исключения.

function ExceptionParam: String

Возвращает текст сообщения об ошибке.

procedure RaiseException(Err: TIFException; Param: String)

Генерирует исключительную ситуацию.

procedure RaiseLastException

Генерирует последнюю возникшую исключительную ситуацию.

The widely used and a simple thing like exception handling is often done in a wrong way. There are documentation about try/finally and try/except statements of course. And there is a lot of different places around the web where the things are described about a certain use cases. I just wanted to explain my vision about that.

So first I will describe when we usually use the try statements then the thing how we should not use them and after that I will try to illustrate different usage patterns for the try/finally/except statements.

When to use

We only care about to use of these blocks when we know that the exceptions can be raised while executing our code. So usually we are not going to guard the local variable assignment or IF operator which compares the value of the parameter with constant. I say usually, because even such a statements can throw the exception – it all depends on the context and implementation. When you have operator overloading you can get the exception thrown from the operator overloading method. Or when using the variant types the conversion will fail for a certain data type with EVariantCastError or any other similar cases.

The use of such try/finally and try/except constraints starts when we deal with resources! So what’s the resources:

  • Allocating memory for a pointer;
  • Constructing the instance of the class (there I talk about the standard TObject descendants with the usual lifetime);
  • Allocating any system resources like handles, tokens, critical sections etc;
  • Using some error/finalization logic (when certain tasks should be performed in case of error or always after certain operation was completed).

So when Finally and when Except

This is the simplest thing to solve. You need except to handle a certain error – so to execute your code in case if certain (or any) exception is raised. And when you need to ensure that a certain part of your code is executed in the end of the some code block then you use finally.

When to not use

Hiding the errors

Try to never suppress the exceptions with the empty except end clause. This is just bad behavior! I will be not the first who writes about this – there are different books about coding style, writing better code etc, maybe also a lot of topics in the internet about this in other programming languages as well. Some core points are clearly enough stated in this stack overflow answer. So please try to avoid the following constructions:

begin
  ...
  try
    DoSomething;
    ...
  except
  end;
  ...
  DoSomethingLater;
end;

Overusing the try statements

Sometimes it can happen that the code which is absolutely safe is put into the try finally/except block and the block itself is extensively used in the loop for a certain processing of the data. Try blocks are not just a empty statements. When you add try blocks the compilers adds extra instructions and handling to your code which actually slows down your code. Of course it’s very small penalty in general code you write every day, but when we are talking about redundant try blocks in some code which is executed billion or quadrillions times – that will be just waste of the CPU cycles!

So consider following example using the loop:

for i := 1 to 10 do
  Write(i);
Readln;

which is translated to the following assembly in Win32:

tryblocks_without_try

Just imagine if we add the redundant try finally block for the loop body:

for i := 1 to 10 do
  try
    Write(i);
  finally
  end;
Readln;

the code generated will much bigger:

tryblocks_with_try

So now you should see the performance penalty of each redundant try block added to your source code.

Generic exception use

When raising the errors in your application, please do not use the code like this:

raise Exception.Create('Something went wrong!');

It’s not about the message but about the type of the exception and the exception handling in general. When you raise such an exception (of base class) the caller cannot handle it in a comfortable way! Sure you can handle it by checking the message of the exception, but that’s not the way we go.

The exceptions should be tied to the context where they can be raise or at least have some logical link. Define exception classes for your error cases like this:

type
  EMyErrorBase = class(Exception);
  
  EMyErrorNoValue = class(EMyErrorBase)
    ...
  end;

  EMyErrorNoInput = class(EMyErrorBase)
    ...
  end;

Again: do not use one exception type for all kind of errors – then you are in the same situation like before (it is of course better than just raising Exception, but still is not good). Build the structure of your exceptions – because even implementation is called SEH (structured exception handling) 🙂

You can check one “fail” unit from VCL library. Just take a look at Vcl.Imaging.pngimage:

  {Error types}
  EPNGOutMemory = class(Exception);
  EPngError = class(Exception);
  EPngUnexpectedEnd = class(Exception);
  EPngInvalidCRC = class(Exception);
  EPngInvalidIHDR = class(Exception);
  EPNGMissingMultipleIDAT = class(Exception);
  EPNGZLIBError = class(Exception);
  EPNGInvalidPalette = class(Exception);
  EPNGInvalidFileHeader = class(Exception);
  EPNGIHDRNotFirst = class(Exception);
  EPNGNotExists = class(Exception);
  EPNGSizeExceeds = class(Exception);
  EPNGMissingPalette = class(Exception);
  EPNGUnknownCriticalChunk = class(Exception);
  EPNGUnknownCompression = class(Exception);
  EPNGUnknownInterlace = class(Exception);
  EPNGNoImageData = class(Exception);
  EPNGCouldNotLoadResource = class(Exception);
  EPNGCannotChangeTransparent = class(Exception);
  EPNGHeaderNotPresent = class(Exception);
  EPNGInvalidNewSize = class(Exception);
  EPNGInvalidSpec = class(Exception);

That’s not the way it should be done! Why? Imagine you load the TPNGImage from stream and want to check if it fails because of the PNG format error and not because of any other error – then you have only one chance to handle that like this:

try
  SomePngImage.LoadFromStream(SomeStream);
except
  on E: Exception do
    if not E.ClassName.StartsWith('EPNG') then
      raise;
end;

Stringly-typed! If they would define the EPNGException (or use ENGError, whatever) and then descend all of their exceptions from it then it will much cleaner and better!

Usage patterns

When using the try blocks in most of the cases developers do not need to think about the way they use the statement because they already have the patterns in their mind and do that automatically. But while that’s true for the experienced developers (which I assume are not the target pool of this post :)) it is not true for the beginners.

Why is this important? Because using the try blocks correctly can and will avoid the following issues in your applications:

  • Hidden errors which are hard to detect in production system (because they are just suppressed);
  • Memory leaks of the objects created in code which “believes” that the code between creation and releasing of the instance will never fail (that is very popular issue between all kind of developers – even the experienced ones sometimes trust too much in their code);
  • Resource leaks of the handles/tokens and other stuff (because of the same reason);
  • Performance penalty when overusing the try statements;
  • Application debugging hell, when many things are exception-validated (kind of exception-driven checks) .

Creating the object

var
  X: TObject;
begin
  X := TObject.Create;
  try
    // Do the business with the X
  finally
    X.Free;
  end;
end;

Notes:

  • Always create before try (outside of the try/finally block);
  • Do not put any code between Create and try.

Creating multiple objects

var
  x1, x2, x3: TObject;
begin
  ...
  x1 := nil;
  x2 := nil;
  x3 := nil;
  try
    ...
    x1 := TObject.Create;
    ...
    x2 := TObject.Create;
    ...
    x3 := TObject.Create;
    ...
  finally
    x3.Free;
    x2.Free;
    x1.Free;
  end;
end;

Notes:

  • Set all objects to nil before the try block;
  • Release all objects in finally (preferably in reverse order of creation);
  • Do not check for nil in finally as it is already done in Free method of RTL!

Adding the object to a collection

type
  TCar = class //non interfaced
  end;
...
var
  X: TCar;
  L: TObjectList<TCar>;
begin
  ...
  X := TCar.Create;
  try
    // Do any business needed before adding to the collection
    L.Add(X);
  except
    X.Free;
    raise;
  end;
end;

Notes:

  • Never have the code between adding the object to the collection and except clause;
  • Do not forget the raise in the except clause!

The following line:

L.Add(TCar.Create);

is a potential memory leak. Sure I am a bit paranoid there, but if you get any exception in Add method the TCar instance will be leaked!

Working with a file

var
  F: TFileStream;
begin
  F := TFileStream.Create('C:\..', fmOpenRead or fmShareDenyNone);
  try
    // Do your job with the file stream
  finally
    F.Free;
  end;
end;

Creating the interfaced object and passing it as interface

type
  TMultiService = class(TInterfacedObject, IService1, IService2)
  private
    ...
  end;

  TSlave = class(..., ISlave)
  public
    constrctor Create(A1: IService1; A2: IService2);
  end;

var
  M: TMultiService;
  S: ISlave;
begin
  M := TMulti.Create;
  try
    S := TSlave.Create(M, M);
    M := nil;
    { Do any other business there }
    ...
  except
    M.Free;
    raise;
  end;
end;

In this case once you constructed the TInterfacedObject and assigned it to the variable of object type (TMultiService) it will be not disposed automatically. That’s why you should apply standard try/finally pattern there. But there is one thing to remember. Once you passed the variable of object type (in our case M) to a variable or parameter of interface type (IService1, IService2) the lifetime of the instance from that moment is managed by the reference counting.

Notes:

  • Avoid using the CONST prefix for the interface type parameters if your method is going to accept direct constructors of the interfaced objects like “Method(TService.Create)” (as when you write TService.Create the reference is not set, Delphi just constructs the TInterfacedObject with FRefCount = 0. And now when you pass it to the “const” parameter and it is never used inside of the routine you will get the memory leak as the reference counting did not took place! And this is not a bug – it’s the way how it works and should).
  • Once the instance is reference counted never call Free on the instance;
  • Protect passing the object-type variable to interface with try/finally blocks;
  • You do not need “M := nil;” if you do not do anything after passing the object-type variable;
  • You do not need to apply this mechanism in standard cases when you can just pass TService1.Create and TService2.Create (assuming they implement related interfaces) as a parameters – as this will be absolutely safe:
    • If the exception happens in the constructor call the instance is released automatically anyway;
    • Once the constructor succeeded the value is already reference counted as the parameter types are interfaces.

Thank you for reading this post. I hope you learned something new and this information was of some use.

Оператор
try
except
имеет вид:

try
 
операторы
except
 
блок обработки исключений
end;

Блок
try
называется защищаемым блоком. Если
при выполнении программы в нем происходит
ошибка, то он завершается и выполнение
передается блоку except.
Если исключение обрабатывается в блоке
except,
то после его обработки программа
продолжает выполняться с оператора,
следующего за try
except
end.
Если исключение остается необработанным
и имеется объемлющий блок try,
то выполнение передается его блоку
except.
Если объемлющего блока try
нет, то программа завершается с ошибкой.
Наконец, если в блоке try ошибки не
произошло, то блок except
игнорируется и выполнение программы
продолжается дальше.

Если
в процессе обработки исключения (в блоке
except)
произошло другое исключение, то текущий
блок except
завершается, первое исключение остается
необработанным и обработка нового
исключения передается объемлющему
блоку try.
Таким образом, в каждый момент времени
существует максимум одно необработанное
исключение.

Блок
обработки исключений
представляет
собой либо последовательность операторов,
разделенных точкой с запятой, либо
последовательность обработчиков
исключений вида

on
имя:
тип do
оператор;

где
тип — тип исключения (должен быть
производным от типа Exception),
имя — имя переменной исключения (имя
с последующим двоеточием может быть
опущено). В первом случае при обработке
исключения выполняются все операторы
из блока except.
Во втором случае среди обработчиков
осуществляется поиск типа текущего
исключения, и если обработчик найден,
то выполняется соответствующий оператор
обработки исключения, в противном случае
исключение считается необработанным
и передается объемлющему блоку try.

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

Поиск
типа исключения в обработчиках
производится с учетом наследования:
исключение будет обработано, если оно
принадлежит к указанному в обработчике
типу или производному от него. Поэтому
принято записывать вначале обработчики
производных классов, а затем — обработчики
базовых (в противном случае обработчик
исключения производного класса никогда
не сработает). Обработчик исключения
Exception
обрабатывает все возможные исключения
и поэтому должен быть записан последним.

Пример.

try

 
readln(x);
 
writeln(ctg(x)/(x-1));
 

except
 
on
EZeroDivide do
   
writeln(‘Деление
на
0’); 
 
on
e: ECtgError do
   
writeln(e.Message); 
end;

Оператор try … Finally

Оператор
try
finally
имеет вид:

try
 
операторы
finally
 
операторы
end;

Операторы
в блоке finally
выполняются безотносительно к тому,
возникло или нет исключение в блоке
try.
При этом само исключение не обрабатывается.

Блок
finally
используется для возвращения ранее
выделенных ресурсов.

Пример
1.
Закрытие открытого файла.

reset(f);
try
 

finally
 
close(f); 

end;

Файл
будет закрыт независимо от того, произошло
ли ислючение в блоке try.

Пример
2.
Возвращение выделенной динамической
памяти.

New(p);
try
 

finally
 
Dispose(p);
end;

Динамическая
память, контролируемая указателем p,
будет возвращена независимо от того,
произошло ли ислючение в блоке try.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

Классы исключений определены в модулях Classes и SysUtils. Используются для обработки исключительных ситуаций, возникающих при исполнении программы. Например, попытка открыть файл, которого нет.

Базовый класс исключений – Exception. Его потомки – классы исключений, имена которых начинаются с символа E, за которым следует описание исключения на английском языке.

В модуле SusUtils опредлено много исключений. Вот примеры:

  • EIntError – ошибка операции с целыми числами.
  • EZeroDivide – деление на ноль.
  • ERangeError – ошибка диапазона.
  • EIntOverflow – ошибка переполнения.
  • EMathError – математическая ошибка.

Для обработки исключений предусмотрены три инструкции:

  • инструкции Raise,
  • блоки try…except,
  • блоки try…finally.

Внимание. Эти инструкции доступны в режимах Delphi и ObjPas. В режиме Turbo Pascal они невозможны.

Инструкция Raise

Инструкция Raise запускает исключение, которое предварительно должно быть определено, как экземпляр класса исключений. Формат инструкции:

Raise <Экземпляр класса>;

При возникновении объявленного исключения инициализируется экземпляр класса этого исключения. Пример обработки деления на ноль, когда этому исключению дано уникальное имя:

Type EDivException = Class(Exception);

Function DoDiv (X,Y : Longint) : Integer;

begin

If Y=0 then

Raise EDivException.Create (’Division by Zero would occur’);

Result := X Div Y;

end;

Инструкция try…except

Формат инструкции:

try                                         // Попробуй
< Инструкции >

except                                   // Исключения
<обработчики исключений>

end;                                             // Конец

Алгоритм инструкции:

  • Выполняется секция try.
  • Если исключение не возникает, то инструкция завершается.
  • Если исключение возникает, то секция try завершается и выполняется обработчик возникшего исключения из секции except.

Формат обработчика исключений в секции except

on

<класс исключения>

do < Инструкция >;                  // Любая инструкция, кроме goto

end;

Пример обработки деления на ноль (использованы  средства из предыдущего примера):

Try

Z := DoDiv (X,Y);

Except

On EDivException do Z := 0;

end;

Инструкция try…finally

В ней при возникновении исключения секция try завершается, исключение специально не обрабатывается, но всегда исполняется секция finally. Формат инструкции:

try                              // Попробуй

<Инструкции >

finally                          // Финально

< Инструкции >

end;                                 // Конец

Алгоритм инструкции

  • Выполняется группа try.
  • Если исключение возникло, то все инструкции, следующие после инструкции, вызвавшей исключение, пропускаются, и управление передается первой инструкция секции finally.

Если исключений не возникло, то секция finally все равно выполняется.

TwitterSEO CommunityВаау!News2.ruChippKoricaSMI2Google BookmarksDiggI.uaЗакладки YandexLinkstoreMyscoopCommunizmRu-marksWebmarksRuspaceLinkomaticKli.kzWeb-zakladkaZakladok.netRedditdeliciousMa.gnoliaTechnoratiSlashdotYahoo My WebБобрДобр.ruMemori.rurucity.comМоёМесто.ruMister Wong








English (en)



suomi (fi)





















Free Pascal supports exceptions. Exceptions are useful for error handling and avoiding resource leaks. However, bear in mind that exceptions have a performance impact.

The official documentation is here: Reference guide chapter 17.

By default exceptions are disabled. You can opt in by using the ObjFPC or DELPHI Compiler Mode, or adding -Sx to the compiler commandline. This enables the try, raise, except, and finally reserved words for use in your own code, but it doesn’t enable exception generation from the RTL. To enable the RTL to raise exceptions instead of generating runtime errors, use the SysUtils unit in your program.

The base Exception class is found in the SysUtils unit. All exceptions should preferably use this class or its descendants.

SysUtils automatically sets up a basic exception catcher, so any otherwise uncaught exception is displayed in human-readable form, as the program terminates. To generate readable callstacks from caught exceptions in your own code, without necessarily terminating the program, you can use SysUtils functions ExceptAddr, ExceptFrames, ExceptFrameCount, and BackTraceStrFunc.

Examples

Note that Pascal uses different keywords than some other languages: raise instead of throw, and except instead of catch. Also, as a compiler design choice, each try-block can pair with either an except or finally block, but not both at the same time. You’ll need to nest try-blocks if you need except and finally protecting the same code.

Error handling

uses sysutils;

begin
  try
    // Do something that might go wrong.
  except
    begin
      // Try to recover or show the user an error message.
    end;
  end;
end.

Cleaning up resources

try
  // Do something that might go wrong.
finally
  // Clean-up code that is always called even if an exception was raised.
end;

Exception leaking

Finally-blocks don’t destroy exception objects. Any exception that reaches the program’s «end.» will trigger a memory leak warning. An extra except block can be used to consume such exceptions. This is Delphi-compatible.

begin
  try
    // Your main program, where an exception object is created.
  finally
    try
      // Clean-up code.
    except
    end;
  end;
end.

Signalling problems

raise Exception.Create('Helpful description of what went wrong');

Using specialised exception types to signal different problems

type EMyLittleException = Class(Exception);

begin
  try
    raise EMyLittleException.Create('Foo');
  except
    on E : EMyLittleException do writeln(E.Message);
    on E : Exception do writeln('This is not my exception!');
    else writeln('This is not an Exception-descendant at all!');
  end;
end;

Re-raising exceptions

try
  // Do something that might go wrong.
except
  // Try to recover or show the user an error message.
  if recoveredSuccessfully = FALSE then
    raise;
end;

Exception classes

SysUtils defines and raises many specific exception classes.

With SysUtils included in your program, and exceptions enabled, various runtime errors are changed into exceptions. Processor-level interrupts like SIGSEGV or SIGFPU, which normally translate to run-time errors, are also changed to exceptions. For example, run-time error 200 (division by zero) becomes EDivByZero or EZeroDivide, while run-time error 216 (a general protection fault) becomes EAccessViolation.

Best practice

  • Raise exceptions to signal that an operation could not be completed, where it normally should have succeeded.
  • Do not use exceptions as part of expected control flow. Instead, add checks for common error conditions and return error values the old-fashioned way. For example, input parsing problems or file existence fails are usually not truly exceptional.
  • But do raise an exception if it’s critical that the error is noticed; programmers may forget to check for returned error values.
  • Naming convention: Prefix exception class names with a capital E.
  • Re-raise exceptions in except-blocks using raise; if you were unable to recover from the problem; this preserves the original exception’s callstack.
  • Be careful about using the catch-all base Exception class, since the underlying code or OS/filesystem might produce an unanticipated exception that slips through your exception handler, potentially corrupting the program state.
  • When writing a unit or library, if exceptions are used at all, they should be documented clearly up front. This way the unit user knows what to expect.
  • Keep exception handling away from code that needs to run as fast as possible.

Performance

Compiler optimisation levels don’t make much difference. All exception blocks use a small amount of wrapper code that can’t be optimised away. There are different ways for a compiler to produce exception code, with varying performance implications. As of FPC 3.0.4, the default exception mechanism uses the standard setjmp/longjmp style. FPC also supports OS-provided Structured Exception Handling; this is the default on Win64, and can be enabled on Win32 (recompile the compiler with $define TEST_WIN32_SEH). Other mechanisms may be added to FPC in the future.

To get a better feel for what’s going on, try writing a small test program and compiling it with the -a switch. This leaves behind a human-readable assembly file.

Notes on the setjmp/longjmp method:

  • Try-statements insert some extra address calculations, a stack push and pop, some direct jumps, and a conditional jump. This is the setup cost of an exception frame, even if no exception is raised.
  • Except-statements insert the above, plus a push and pop, more direct jumps, and another conditional jump.
  • Finally-statements add a pop and a conditional jump.
  • Raise-statements create a string and pass control to FPC’s exception raiser. The string creation itself can spawn an implicit exception frame for the function/method, due to dynamic string allocations, even if the raise is not executed. This is why in e.g. container types one often sees the raise moved to a separate local (private) procedure. This localises the exception frame to that procedure, so the performance hit is only taken if the procedure is called.

Furthermore, generating a human-readable callstack from a raised exception involves time-consuming stack traversal and string manipulation.

With all that said, outside of heavy processing code, the convenience of exceptions usually outweighs their performance impact. Don’t feel bad about using them if it makes your code better.

Further reading

Logging exceptions

Avoiding implicit try finally section

Exceptions vs Error codes — a situation where exceptions might be a danger in some code (see middle of this page)

Понравилась статья? Поделить с друзьями:
  • Обс выдает ошибку при запуске записи
  • Обрешетка под металлочерепицу ошибки
  • Обс во время записи произошла неопознанная ошибка
  • Обряд на исправление ошибок черная магия
  • Обрыв ремня вентилятора ошибка