[...] because finally section should be executed even with continue or exit.
Yes, the
finally section is executed even on
continue,
break or
exit.
Not even
goto can bypass
finally (because attempting to use
goto in an exception block leads to a compiler error) - so unless the process is killed by the OS or some nasty stack corruption occurs, there seems to be no way for
finally to fail. Please correct me if I'm wrong!
Second question is aren't there any loss of performance, which I don't really care. But just want to know.
Like
@Thaddy already said,
try..finally does negatively impact performance. Take a look at the disassembly of these two functions differing only in the use of
try..finally (compiled on
x86_64-linux using
FPC 3.2.2):
procedure Test_1();
begin
Foo();
Bar();
Fizz();
end;
lea rsp, [rsp - 8]
call Foo
call Bar
call Fizz
lea rsp, [rsp + 8]
ret
Very straightforward disassembly, does pretty much exactly what you'd expect.
Now watch what happens when we add a
try..finally block:
procedure Test_2();
begin
try
Foo();
Bar();
finally
Fizz();
end;
end;
; allocate space for jump buffer
lea rsp, [rsp - 104]
; jmp_buf = fpc_pushexceptaddr(Ft = 1 {finally}, _buf = local@+24, _newaddr = local@+0)
mov rdx, rsp
lea rsi, [rsp + 24]
mov edi, 1
call fpc_pushexceptaddr
mov rdi, rax
; fpc_setjmp(jmp_buf); saves all nonvolatile registers (expensive!) and returns 0 in eax
call fpc_setjmp
; !!! raising an exception moves the instruction pointer here (with eax <> 0)
movsxd rdx, eax
mov QWORD PTR [rsp + 88], rdx
; execute body only right after fpc_setjmp (when eax = 0)
test eax, eax
jne .finally
; 'try' body
call Foo
call Bar
; forget about jmp_buf (counterpart to fpc_pushexceptaddr) and execute 'finally' block
.finally
call fpc_popaddrstack
; 'finally' body
call Fizz
; re-raise caught exception if one occurred (when eax <> 0 after fpc_setjmp)
mov rax, QWORD PTR [rsp + 88]
test rax, rax
je .done
call fpc_reraise
; end of try..finally block - return from procedure
.done
lea rsp, [rsp + 104]
ret
Merely setting up an exception handler (i.e. entering a
try block) is expensive in Pascal, which handles exceptions differently than C++ does:
- C++ exception handlers are zero-cost to set up, but exceptions are very expensive to catch.
- Pascal (FPC) exception handlers are somewhat costly to set up, but exceptions are not too expensive to catch.
In other words, using
try..finally/except always has a performance impact, even if no exceptions are actually raised.
There's a tradeoff between
"happy path" speed and
"unhappy path" speed, and the FPC developers chose balance over imbalance.