在可见性有限的 `noexcept` 函数中调用 `std::terminate` - gcc vs clang codegen
`std::terminate` invocation in a `noexcept` function with limited visibility - gcc vs clang codegen
考虑以下代码片段:
void f();
void a() { f(); }
void b() noexcept { f(); }
在上面的场景中,f
的主体在当前翻译单元中对编译器不可见。因此,由于 b
被标记为 noexcept
,因此必须在调用方生成额外的代码以确保捕获异常并调用 std::terminate
。
这就是 clang++ -Ofast -std=c++2a
所做的 (主干版本):
a(): # @a()
jmp f() # TAILCALL
b(): # @b()
push rax
call f()
pop rax
ret
mov rdi, rax
call __clang_call_terminate
__clang_call_terminate: # @__clang_call_terminate
push rax
call __cxa_begin_catch
call std::terminate()
但是g++ -Ofast -std=c++2a
没有(trunk版):
a():
jmp f()
b():
jmp f()
g++
是如何摆脱困境的?不应该在调用方生成代码,因为 f
的主体不可见吗?
...或者这只是 Compiler Explorer 的怪癖?
正如@ach 回答的那样,有一个 bug opened on gcc bug tracker。但如果我可以这么说,这不是主要问题。
影响是异常会泄漏而不是终止程序但是:
- 如果你想在未捕获的异常上调用终止,那已经是这样了
- 如果你想要不抛出异常的安全保证,那不提供。
我唯一能想到的就是在开发的时候。或者在环境中,如果合同被破坏(当生命受到威胁时)必须失败;在这种情况下,编译器和要使用的功能受到严格控制。
Andrzej 在 his article - noexcept — what for ?
中表现出色
考虑以下代码片段:
void f();
void a() { f(); }
void b() noexcept { f(); }
在上面的场景中,f
的主体在当前翻译单元中对编译器不可见。因此,由于 b
被标记为 noexcept
,因此必须在调用方生成额外的代码以确保捕获异常并调用 std::terminate
。
这就是 clang++ -Ofast -std=c++2a
所做的 (主干版本):
a(): # @a()
jmp f() # TAILCALL
b(): # @b()
push rax
call f()
pop rax
ret
mov rdi, rax
call __clang_call_terminate
__clang_call_terminate: # @__clang_call_terminate
push rax
call __cxa_begin_catch
call std::terminate()
但是g++ -Ofast -std=c++2a
没有(trunk版):
a():
jmp f()
b():
jmp f()
g++
是如何摆脱困境的?不应该在调用方生成代码,因为 f
的主体不可见吗?
...或者这只是 Compiler Explorer 的怪癖?
正如@ach 回答的那样,有一个 bug opened on gcc bug tracker。但如果我可以这么说,这不是主要问题。
影响是异常会泄漏而不是终止程序但是:
- 如果你想在未捕获的异常上调用终止,那已经是这样了
- 如果你想要不抛出异常的安全保证,那不提供。
我唯一能想到的就是在开发的时候。或者在环境中,如果合同被破坏(当生命受到威胁时)必须失败;在这种情况下,编译器和要使用的功能受到严格控制。
Andrzej 在 his article - noexcept — what for ?
中表现出色