Difference between revisions of "Exceptions"
CuriousKit (talk | contribs) m (Reformatting) |
m (→Performance) |
||
Line 87: | Line 87: | ||
* <code>Finally</code>-statements add a pop and a conditional jump, to allow re-raising an exception. | * <code>Finally</code>-statements add a pop and a conditional jump, to allow re-raising an exception. | ||
* <code>Raise</code>-statements create a string and pass control to FPC's exception raiser. | * <code>Raise</code>-statements create a string and pass control to FPC's exception raiser. | ||
+ | * <code>Raise</code> statements also create an exception frame for that function/method, even if the Raise statement is not executed. This is why in e.g. container types one often sees the raise command moved to a separate local (private) procedure. That procedure then has the exception frame, but only when actually executed. | ||
Furthermore, if you want a human-readable callstack to aid debugging (using SysUtils functions <code>ExceptAddr</code>, <code>ExceptFrames</code>, <code>ExceptFrameCount</code>, and <code>BackTraceStrFunc</code>), that involves time-consuming stack traversal and string manipulation. | Furthermore, if you want a human-readable callstack to aid debugging (using SysUtils functions <code>ExceptAddr</code>, <code>ExceptFrames</code>, <code>ExceptFrameCount</code>, and <code>BackTraceStrFunc</code>), that 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. | 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 == | == Further reading == |
Revision as of 13:21, 2 August 2018
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: [1].
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
keywords 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 in SysUtils: [2]. All exceptions in code should preferably use this class or its descendants.
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
try
// Do something that might go wrong.
except
on E : Exception do begin
// Try to recover or show the user an error message.
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;
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!');
end;
end;
Exception classes
SysUtils defines and raises many specific exception classes: [3]
Best practice
As exceptions impose a performance penalty, some general guidelines apply:
- Raise exceptions to signal that an operation that should have succeeded could not be completed.
- 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.
- 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 usingraise;
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. - 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 come with a small amount of wrapper code that can't be optimised away.
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.
Try
-statements insert some extra address calculations, a stack push and pop, some direct jumps, and a conditional jump.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, to allow re-raising an exception.Raise
-statements create a string and pass control to FPC's exception raiser.Raise
statements also create an exception frame for that function/method, even if the Raise statement is not executed. This is why in e.g. container types one often sees the raise command moved to a separate local (private) procedure. That procedure then has the exception frame, but only when actually executed.
Furthermore, if you want a human-readable callstack to aid debugging (using SysUtils functions ExceptAddr
, ExceptFrames
, ExceptFrameCount
, and BackTraceStrFunc
), that 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
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)