请参阅以下代码,其中包含调用另一个抛出函数的函数的 3 个实现。
# 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;
}
根据我对规范的理解noexcept
,g2
和g3
是严格等效的。但是,当我使用 GCC 在编译器资源管理器中编译它时,生成的代码对于g1
and是严格等效的g2
,但对于 则不是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
为什么 ?
GCC 中实现异常的方式,不需要为
noexcept
和throws
检查发出额外的代码。编译器创建几个表,其中包含有关所有函数、它们的堆栈变量以及允许它们抛出的异常的信息。当抛出异常时,此信息用于展开调用堆栈。noexcept
在堆栈展开期间,标准库会注意到堆栈中有一个条目并调用std::terminate
. 因此和之间存在差异,但它不在生成的二进制文件的部分中,而是在,或中的某处。godbolt.org 没有显示这些内容。g1
g2
.text
.eh_frame
eh_frame_hdr
.gcc_except_table
如果您执行包含在
try-catch
from中的这些函数main
,您会发现,尽管代码与g2
相比没有任何额外的内容g1
,但执行不会到达 inmain
及std::terminate
更早的 catch 子句。粗略地说,在执行std::terminate
时会发生这种情况。throw
f
至于为什么
g3
代码不同,可能是因为优化器无法查看所有涉及的异常处理逻辑,因此没有对最初生成的代码进行太多更改。编辑:实际上,如果禁用指令过滤器,godbolt.org 可以显示填充表的相关 ASM 指令。
与
注意额外的行
阅读 cppreference.com 上的 noexcept 参考,我得出的结论是,编译后的代码不受
noexcept
它只是编译时评估,因此如果单独使用,不会影响编译器的输出。