Sign in Join
Wiki copytemporary

Avoiding implicit try finally section: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: Avoiding implicit try finally section

OldNewLine
1+## Overview
2+
3+When optimizing code it helps to know that the [[Compiler|compiler]] will wrap certain code constructs in an implicit [[Try|try]] … [[Finally|finally]] statement. This is needed whenever you use [[Variable|variables]] such as [[Ansistring|AnsiString]], [[Variant]] or [[Dynamic array|dynamic arrays]] which require [[Initialization|initialization]] and [[Finalization|finalization]] (i.e. where the standard [[Procedure|procedures]] `initialize` and `finalize` are needed for correct allocation and release of acquired memory).
4+
5+For example, a procedure like
6+
7+``` pascal
8+procedure doSomething;
9+var
10+ msg: ansiString;
11+begin
12+ // do something with msg
13+end;
14+```
15+
16+is actually expanded by the compiler to look like this (difference highlighted):
17+
18+``` pascal
19+procedure doSomething;
20+var
21+ msg: ansiString;
22+begin
23+ initialize(msg);
24+ try
25+ // do something with msg
26+ finally
27+ finalize(msg);
28+ end;
29+end;
30+```
31+
32+The compiler thereby ensures that the reference count of `msg` will be properly decremented when `procedure doSomething` exits with [[Exceptions|exception]]. However, often this may have significant adverse effects on the generated code's speed.
33+
34+This is issue was a subject on the `fpc-devel` list in the [`TList` slowness classes](http://www.mail-archive.com/fpc-devel@lists.freepascal.org/msg01367.html) thread.
35+
36+Note, that temporary `ansiString` variables can be created *implicitly*. The only way to be completely certain about what actually is being done is to read the [[Assembler|assembler]] output.
37+
38+## Possible solutions
39+
40+- use [[$implicitExceptions|{$implicitexceptions off}]]: Ensure this applies to release versions only. Debugging can become cumbersome with that switch especially locating memory leaks and corruption.
41+- split off rarely used code that causes an implicit `try…finally` into separate procedures. (You can use nested procedures)
42+- use [[Const|const parameters]] rather than value parameters. This avoids the need to change `refcount` but temporary variables could still be an issue.
43+- use [[Global variables|global variables]]: You have to be careful with reentrancy issues here though and temporary variables could still be an issue.
44+- use non-reference-counted types like [[ShortString|shortstring]].
45+
46+## Risks and when to apply
47+
48+> **Warning:** These exception frames are generated for a reason. If you leave them out any exception in that code will leave a memory leak
49+
50+In 2007 [[sImplicitExceptions|{$implicitExceptions}]] was added to the [`strutils`](https://www.freepascal.org/docs-html/rtl/strutils/index.html) [[Unit|unit]]. For this, the following approach was followed:
51+
52+- A [[Routine|routine]] that calls a routine that [[Raise|raises]] exceptions is unsafe – e.g. [`strToInt`](https://www.freepascal.org/docs-html/rtl/sysutils/strtoint.html), but not [`strToIntDef`](https://www.freepascal.org/docs-html/rtl/sysutils/strtointdef.html).
53+- A routine that raises exceptions itself is unsafe.
54+- Very large routines are not worth the trouble, because of the risk and low gains – e.g. [date formatting](https://www.freepascal.org/docs-html/rtl/sysutils/datetimeroutines.html) routines.
55+- Floating point usage can raise exceptions that are converted into catchable exceptions by [[sysutils|sysUtils]]. I'm not sure if this really is sufficient reason, but I skipped floating point using routines initially for this reason.
56+
57+If you detect problems with these changes please contact Marco.
58+
59+## Demo program
60+
61+Below is a small demo [[Program|program]] that
62+
63+- When run, clearly shows that avoiding an implicit `try … finally`-block can make code a lot faster. When I run this program on my system, I get
64+
65+`time of fooNormal: 141`\
66+`time of fooFaster: 17`
67+
68+- Shows a trick how to avoid implicit `try … finally`-block (without changing the meaning or safety of the code) in some cases (when you don't need to actually use that [[Ansistring|AnsiString]]/[[Variant]]/[[Data type|something]] every time procedure is called but e.g. only if some parameter has some particular value).
69+
70+``` pascal
71+program implicitExceptionDemo;
72+
73+// for exceptions
74+{$mode objfpc}
75+// data type 'string' refers to 'ansistring'
76+{$longstrings on}
77+
78+uses
79+ {$IFDEF UNIX}
80+ BaseUnix, Unix, // needed only to implement clock()
81+ {$ENDIF}
82+ sysUtils;
83+
84+function clock(): int64;
85+{$IFDEF UNIX}
86+var
87+ dummy: tms;
88+begin
89+ clock := fpTimes(dummy);
90+end;
91+{$ELSE}
92+var
93+ TS: TTimeStamp;
94+begin
95+ TS := DateTimeToTimeStamp(Now);
96+ result := TS.Time;
97+end;
98+{$ENDIF}
99+
100+// When fooNormal() and fooFaster() are called
101+// i is always >= 0, so no exception is ever actually raised,
102+// and string constants are not really used.
103+
104+procedure fooNormal(i: integer);
105+var
106+ s: string;
107+begin
108+ if i = -1 then
109+ begin
110+ s := 'Some operation with AnsiString';
111+ raise Exception.Create(s);
112+ end;
113+end;
114+
115+procedure fooFaster(i: integer);
116+ //
117+ procedure RaiseError;
118+ var
119+ s: string;
120+ begin
121+ s := 'Some operation with AnsiString';
122+ raise Exception.Create(s);
123+ end;
124+ //
125+begin
126+ if i = -1 then
127+ begin
128+ RaiseError;
129+ end;
130+end;
131+
132+// M A I N =================================================
133+const
134+ testCount = 10000000;
135+var
136+ i: integer;
137+ start: int64;
138+begin
139+ // normal version
140+ start := clock();
141+ for i := 0 to testCount do
142+ begin
143+ fooNormal(i);
144+ end;
145+ writeLn('time of fooNormal: ', clock() - start);
146+
147+ // faster version
148+ start := clock();
149+ for i := 0 to testCount do
150+ begin
151+ fooFaster(i);
152+ end;
153+ writeLn('time of fooFaster: ', clock() - start);
154+end.
155+```
156+
157+By putting `raiseError` into a nested [[Scope|scope]] of `fooFaster`, exception handling does not become part of the main thread of execution.
158+
159+---
160+*From the Free Pascal wiki, page "Avoiding implicit try finally section", as it stood on 2024-01-22 after a change by N7800, 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).*
161+Category: Software security

Back to history