Exceptions: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: Exceptions
| Old | New | Line | |
|---|---|---|---|
| 1 | + | [[FPC|Free Pascal]] supports exceptions. Exceptions are useful for error handling and avoiding resource leaks. However, bear in mind that exceptions have a performance impact. | |
| 2 | + | ||
| 3 | + | The official documentation is here: [Reference guide chapter 17](https://www.freepascal.org/docs-html/ref/refch17.html). | |
| 4 | + | ||
| 5 | + | By default exceptions are disabled. You can opt in by using the [[Mode ObjFPC|ObjFPC]] or [[Mode Delphi|DELPHI]] [[Compiler Mode]], or adding -Sx to the [[Compiler|compiler]] [[Command-line interface|commandline]]. This enables the [[Try|try]], [[Raise|raise]], [[Except|except]], and [[Finally|finally]] [[Reserved word|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 error|runtime errors]], use the SysUtils unit in your [[Program|program]]. | |
| 6 | + | ||
| 7 | + | The base `Exception` [[Class|class]] is found in the [SysUtils](https://www.freepascal.org/docs-html/rtl/sysutils/exception.html) unit. All exceptions should preferably use this class or its descendants. | |
| 8 | + | ||
| 9 | + | 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`. | |
| 10 | + | ||
| 11 | + | While it is possible to raise an exception with any class descending from TObject, it is recommended to use Exception as the basis of exception class objects: the Exception class introduces properties to associate a message and a help context with the exception being raised. What is more, the SysUtils unit sets the necessary hooks to catch and display unhandled exceptions: in such cases, the message displayed to the end user, will be the message stored in the exception class. | |
| 12 | + | ||
| 13 | + | On Windows with SEH using try … except-blocks (without using the SysUtils unit) will not behave as expected (from FPC 3.2.0 on). This due to a bug that will, in all probability be fixed at some stage. In the mean time, the very simple solution is to "use SysUtils". | |
| 14 | + | ||
| 15 | + | ## Examples | |
| 16 | + | ||
| 17 | + | Note that Pascal uses different [[Keyword|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. | |
| 18 | + | ||
| 19 | + | ### Error handling | |
| 20 | + | ||
| 21 | + | ``` pascal | |
| 22 | + | uses sysutils; | |
| 23 | + | ||
| 24 | + | begin | |
| 25 | + | try | |
| 26 | + | // Do something that might go wrong. | |
| 27 | + | except | |
| 28 | + | begin | |
| 29 | + | // Try to recover or show the user an error message. | |
| 30 | + | end; | |
| 31 | + | end; | |
| 32 | + | end. | |
| 33 | + | ``` | |
| 34 | + | ||
| 35 | + | ### Cleaning up resources | |
| 36 | + | ||
| 37 | + | ``` pascal | |
| 38 | + | try | |
| 39 | + | // Do something that might go wrong. | |
| 40 | + | finally | |
| 41 | + | // Clean-up code that is always called even if an exception was raised. | |
| 42 | + | end; | |
| 43 | + | ``` | |
| 44 | + | ||
| 45 | + | ### Exception leaking | |
| 46 | + | ||
| 47 | + | 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](https://bugs.freepascal.org/view.php?id=33650). | |
| 48 | + | ||
| 49 | + | ``` pascal | |
| 50 | + | begin | |
| 51 | + | try | |
| 52 | + | // Your main program, where an exception object is created. | |
| 53 | + | finally | |
| 54 | + | try | |
| 55 | + | // Clean-up code. | |
| 56 | + | except | |
| 57 | + | end; | |
| 58 | + | end; | |
| 59 | + | end. | |
| 60 | + | ``` | |
| 61 | + | ||
| 62 | + | ### Signalling problems | |
| 63 | + | ||
| 64 | + | ``` pascal | |
| 65 | + | raise Exception.Create('Helpful description of what went wrong'); | |
| 66 | + | ``` | |
| 67 | + | ||
| 68 | + | ### Using specialised exception types to signal different problems | |
| 69 | + | ||
| 70 | + | ``` pascal | |
| 71 | + | type EMyLittleException = Class(Exception); | |
| 72 | + | ||
| 73 | + | begin | |
| 74 | + | try | |
| 75 | + | raise EMyLittleException.Create('Foo'); | |
| 76 | + | except | |
| 77 | + | on E : EMyLittleException do writeln(E.Message); | |
| 78 | + | on E : Exception do writeln('This is not my exception!'); | |
| 79 | + | else writeln('This is not an Exception-descendant at all!'); | |
| 80 | + | end; | |
| 81 | + | end; | |
| 82 | + | ``` | |
| 83 | + | ||
| 84 | + | ### Re-raising exceptions | |
| 85 | + | ||
| 86 | + | ``` pascal | |
| 87 | + | try | |
| 88 | + | // Do something that might go wrong. | |
| 89 | + | except | |
| 90 | + | // Try to recover or show the user an error message. | |
| 91 | + | if recoveredSuccessfully = FALSE then | |
| 92 | + | raise; | |
| 93 | + | end; | |
| 94 | + | ``` | |
| 95 | + | ||
| 96 | + | ## Exception classes | |
| 97 | + | ||
| 98 | + | SysUtils defines and raises many specific [exception classes](https://www.freepascal.org/docs-html/rtl/sysutils/index-4.html). | |
| 99 | + | ||
| 100 | + | With SysUtils included in your program, and exceptions enabled, various [runtime errors](https://www.freepascal.org/docs-html/user/userap4.html) 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. | |
| 101 | + | ||
| 102 | + | ## Best practice | |
| 103 | + | ||
| 104 | + | - Raise exceptions to signal that an operation could not be completed, where it normally should have succeeded. | |
| 105 | + | - 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. | |
| 106 | + | - But do raise an exception if it's critical that the error is noticed; programmers may forget to check for returned error values. | |
| 107 | + | - Naming convention: Prefix exception class names with a capital E. | |
| 108 | + | - Re-raise exceptions in `except`-blocks using `raise;` if you were unable to recover from the problem; this preserves the original exception's callstack. | |
| 109 | + | - 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. | |
| 110 | + | - 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. | |
| 111 | + | - Keep exception handling away from code that needs to run as fast as possible. | |
| 112 | + | ||
| 113 | + | ## Performance | |
| 114 | + | ||
| 115 | + | 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. | |
| 116 | + | ||
| 117 | + | 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. | |
| 118 | + | ||
| 119 | + | Notes on the setjmp/longjmp method: | |
| 120 | + | ||
| 121 | + | - `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. | |
| 122 | + | - `Except`-statements insert the above, plus a push and pop, more direct jumps, and another conditional jump. | |
| 123 | + | - `Finally`-statements add a pop and a conditional jump. | |
| 124 | + | - `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. | |
| 125 | + | ||
| 126 | + | Furthermore, generating a human-readable callstack from a raised exception involves time-consuming stack traversal and string manipulation. | |
| 127 | + | ||
| 128 | + | 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. | |
| 129 | + | ||
| 130 | + | ## Further reading | |
| 131 | + | ||
| 132 | + | [[Logging exceptions]] | |
| 133 | + | ||
| 134 | + | [[Avoiding implicit try finally section]] | |
| 135 | + | ||
| 136 | + | [Exceptions vs Error codes](http://codenewsfast.com/cnf/article/0/permalink.art-ng53q201750) - a situation where exceptions might be a danger in some code (see middle of this page) | |
| 137 | + | ||
| 138 | + | --- | |
| 139 | + | *From the Free Pascal wiki, page "Exceptions", as it stood on 2023-12-22 after a change by Dbannon, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
