noexcept对代码生成的影响

最近修改时间: 2020年9月20日 | 创建时间: 2020年3月2日

在C++代码中,如果我们把每个函数声明都加上noexcept,我们的代码会变得更高效吗? 事情不是这么地简单。 考虑以下代码片段:

int g();

int f() {
  return g();
}

我故意不在此翻译单元(translation unit)中定义g,否则的话编译器会有足够的信息来内联(inline)g的所有内容。 尽管如此,所有主要的C++编译器都能弄清楚f仅包含对g的尾调用,并生成如下代码:

f():
        jmp     g()

现在我们来考虑如下的代码片段:

int g();

int f() noexcept {
  return g();
}

编译器在不知道g是否会抛出异常的情况下被迫在f生成代码来处理异常的确被抛出的情况。下列是不同的编译器生成的汇编码:

msvc

$ip2state$int f(void) DB 02H
        DB      08H
        DB      00H
$cppxdata$int f(void) DB 060H
        DD      imagerel $ip2state$int f(void)

int f(void) PROC                                      ; f, COMDAT
$LN5:
        sub     rsp, 40                             ; 00000028H
        call    int g(void)                         ; g
        npad    1
        add     rsp, 40                             ; 00000028H
        ret     0
int f(void) ENDP                                      ; f

gcc

f():
        sub     rsp, 8
        call    g()
        add     rsp, 8
        ret

clang

f():
        push    rax
        call    g()
        pop     rcx
        ret
        mov     rdi, rax
        call    __clang_call_terminate
__clang_call_terminate:
        push    rax
        call    __cxa_begin_catch
        call    std::terminate()

如何处理C函数

现在我们知道,在noexcept函数中调用非noexcept的函数会产生低效的代码 我们如何处理某些保证不会抛出异常却没有被标记为noexcept的函数呢? 幸运的是,Hana Dusíková已经给我们提供了一个解决方案:

你可以通过将noexcept_cast函数标记为强迫内联(force inline),这样的话即使在debug mode下noexcept_cast函数也不会造成性能损失。

结论

请小心对noexcept使用,特别要注意那些可能会运行用户提供代码的高阶函数(higher-order functions)。