Exceptions: what changed
Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.
| Old | New | Line | |
|---|---|---|---|
| 1 | - | ||
| 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, i.e. exceptions are expensive (in processor time). | |
| 2 | 2 | ||
| 3 | - | ||
| 3 | + | The official documentation is here: [Reference guide chapter 17](<https://www.freepascal.org/docs-html/ref/refch17.html>). | |
| 4 | 4 | ||
| 5 | - | ||
| 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 | 6 | ||
| 7 | - | ||
| 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 | 8 | ||
| 9 | - | ||
| 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 | 10 | ||
| 11 | - | ||
| 11 | + | ||
| 12 | + | 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 | ||
| 13 | - | ||
| 14 | + | 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 | ||
| 15 | 16 | ## Examples | |
| 16 | 17 | ||
| 17 | - | ||
| 18 | + | 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 | ||
| 19 | 20 | ### Error handling | |
| 20 | - | ||
| 21 | - | ||
| 21 | + | ``` | |
| 22 | 22 | uses sysutils; | |
| 23 | - | ||
| 24 | - | ||
| 25 | - | ||
| 26 | - | ||
| 27 | - | ||
| 23 | + | ||
| 28 | 24 | begin | |
| 29 | - | ||
| 30 | - | ||
| 31 | - | ||
| 32 | - | ||
| 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 | 33 | ``` | |
| 34 | 34 | ||
| 35 | 35 | ### Cleaning up resources | |
| 36 | - | ||
| 37 | - | ||
| 36 | + | ``` | |
| 38 | 37 | try | |
| 39 | - | ||
| 40 | - | ||
| 41 | - | ||
| 42 | - | ||
| 38 | + | // Do something that might go wrong. | |
| 39 | + | finally | |
| 40 | + | // Clean-up code that is always called even if an exception was raised. | |
| 41 | + | end; | |
| 43 | 42 | ``` | |
| 44 | 43 | ||
| 45 | 44 | ### Exception leaking | |
| 46 | 45 | ||
| 47 | - | ||
| 48 | - | ||
| 49 | - | ||
| 46 | + | 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>). | |
| 47 | + | ``` | |
| 50 | 48 | begin | |
| 51 | - | ||
| 52 | - | ||
| 53 | - | ||
| 54 | - | ||
| 55 | - | ||
| 56 | - | ||
| 57 | - | ||
| 58 | - | ||
| 59 | - | ||
| 49 | + | try | |
| 50 | + | // Your main program, where an exception object is created. | |
| 51 | + | finally | |
| 52 | + | try | |
| 53 | + | // Clean-up code. | |
| 54 | + | except | |
| 55 | + | end; | |
| 56 | + | end; | |
| 57 | + | end. | |
| 60 | 58 | ``` | |
| 61 | 59 | ||
| 62 | 60 | ### Signalling problems | |
| 63 | - | ||
| 64 | - | ||
| 61 | + | ``` | |
| 65 | 62 | raise Exception.Create('Helpful description of what went wrong'); | |
| 66 | 63 | ``` | |
| 67 | 64 | ||
| 68 | 65 | ### Using specialised exception types to signal different problems | |
| 69 | - | ||
| 70 | - | ||
| 66 | + | ``` | |
| 71 | 67 | type EMyLittleException = Class(Exception); | |
| 72 | - | ||
| 73 | - | ||
| 74 | - | ||
| 75 | - | ||
| 76 | - | ||
| 77 | - | ||
| 78 | - | ||
| 79 | - | ||
| 80 | - | ||
| 81 | - | ||
| 68 | + | ||
| 69 | + | begin | |
| 70 | + | try | |
| 71 | + | raise EMyLittleException.Create('Foo'); | |
| 72 | + | except | |
| 73 | + | on E : EMyLittleException do writeln(E.Message); | |
| 74 | + | on E : Exception do writeln('This is not my exception!'); | |
| 75 | + | else writeln('This is not an Exception-descendant at all!'); | |
| 76 | + | end; | |
| 77 | + | end; | |
| 82 | 78 | ``` | |
| 83 | 79 | ||
| 84 | 80 | ### Re-raising exceptions | |
| 85 | - | ||
| 86 | - | ||
| 81 | + | ``` | |
| 87 | 82 | try | |
| 88 | - | ||
| 89 | - | ||
| 90 | - | ||
| 91 | - | ||
| 92 | - | ||
| 93 | - | ||
| 83 | + | // Do something that might go wrong. | |
| 84 | + | except | |
| 85 | + | // Try to recover or show the user an error message. | |
| 86 | + | if recoveredSuccessfully = FALSE then | |
| 87 | + | raise; | |
| 88 | + | end; | |
| 94 | 89 | ``` | |
| 95 | 90 | ||
| 91 | + | ||
| 92 | + | ||
| 96 | 93 | ## Exception classes | |
| 97 | 94 | ||
| 98 | - | ||
| 95 | + | SysUtils defines and raises many specific [exception classes](<https://www.freepascal.org/docs-html/rtl/sysutils/index-4.html>). | |
| 99 | 96 | ||
| 100 | - | ||
| 97 | + | 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 | 98 | ||
| 99 | + | ||
| 100 | + | ||
| 102 | 101 | ## Best practice | |
| 103 | 102 | ||
| 104 | - | ||
| 105 | - | ||
| 106 | - | ||
| 107 | - | ||
| 108 | - | ||
| 109 | - | ||
| 110 | - | ||
| 111 | - | ||
| 103 | + | * Raise exceptions to signal that an operation could not be completed, where it normally should have succeeded. | |
| 104 | + | * 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. | |
| 105 | + | * But do raise an exception if it's critical that the error is noticed; programmers may forget to check for returned error values. | |
| 106 | + | * Naming convention: Prefix exception class names with a capital E. | |
| 107 | + | * Re-raise exceptions in `except`-blocks using `raise;` if you were unable to recover from the problem; this preserves the original exception's callstack. | |
| 108 | + | * 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. | |
| 109 | + | * 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. | |
| 110 | + | * Keep exception handling away from code that needs to run as fast as possible. | |
| 112 | 111 | ||
| 112 | + | ||
| 113 | + | ||
| 113 | 114 | ## Performance | |
| 114 | 115 | ||
| 115 | - | ||
| 116 | + | 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 | ||
| 117 | - | ||
| 118 | + | 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 | ||
| 119 | - | ||
| 120 | + | Notes on the setjmp/longjmp method: | |
| 120 | 121 | ||
| 121 | - | ||
| 122 | - | ||
| 123 | - | ||
| 124 | - | ||
| 122 | + | * `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. | |
| 123 | + | * `Except`-statements insert the above, plus a push and pop, more direct jumps, and another conditional jump. | |
| 124 | + | * `Finally`-statements add a pop and a conditional jump. | |
| 125 | + | * `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 | ||
| 126 | - | ||
| 127 | + | Furthermore, generating a human-readable callstack from a raised exception involves time-consuming stack traversal and string manipulation. | |
| 127 | 128 | ||
| 128 | - | ||
| 129 | + | 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 | ||
| 130 | 131 | ## Further reading | |
| 131 | 132 | ||
| 132 | 133 | [[Logging exceptions]] | |
| 133 | 134 | ||
| 134 | 135 | [[Avoiding implicit try finally section]] | |
| 135 | 136 | ||
| 136 | - | ||
| 137 | + | [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 | ||
| 138 | 139 | --- | |
| 139 | - | ||
| 140 | + | *From the Free Pascal wiki, page "Exceptions", as the Internet Archive kept it on 2025-05-08, last changed on 2024-10-10. 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).* |
