See the following code containing 3 implementations of a function calling another throwing function.
# include <stdexcept>
void f()
{
throw std::runtime_error("");
}
void g1()
{
f();
}
void g2() noexcept
{
f();
}
void g3() noexcept
{
try{ f(); } catch(...){ std::terminate(); }
}
int main()
{
return 0;
}
In my understanding of the noexcept specification, g2 and g3 are strictly equivalent. But, when I compile it in Compiler Explorer with GCC, the generated code is strictly equivalent for g1 and g2, but not for g3:
g1():
push rbp
mov rbp, rsp
call f()
nop
pop rbp
ret
g2():
push rbp
mov rbp, rsp
call f()
nop
pop rbp
ret
g3():
push rbp
mov rbp, rsp
call f()
jmp .L9
mov rdi, rax
call __cxa_begin_catch
call std::terminate()
.L9:
pop rbp
ret
Why ?
The way exceptions are implemented in GCC, there is no need to emit extra code for
noexceptandthrowschecks. The compiler creates several tables with information about all functions, their stack variables and exceptions they are allowed to throw. When an exception is thrown, this info is used to unwind the call stack. It is during the stack unwinding the standard library will notice that there is anoexceptentry in the stack and callstd::terminate. So there is a difference betweeng1andg2, but it's not in the.textsection of the generated binary, but somewhere in.eh_frame,eh_frame_hdror.gcc_except_table. These are not shown by godbolt.org.If you execute these functions wrapped in
try-catchfrommain, you will observe that indeed, despite the code ofg2not having anything extra compared tog1, the execution will not reach the catch clause inmainandstd::terminateearlier. Roughly speaking, thisstd::terminatewill happen when executingthrowinf.As for why
g3code is different, it's probably because the optimizer couldn't look through all this involved exception handling logic and therefore didn't change the initially generated code much.EDIT: Actually godbolt.org can display related ASM directives that populate the tables if you disable the filter for directives.
vs
Notice the extra lines