在这种情况下,带有 noexcept 的 C++ 函数实际上更慢?

C++ function with noexcept in this case is actually slower?

我正在尝试自己在不同的编译器上试验代码。 我一直在尝试查找禁用某些函数异常的优点(通过二进制足迹)并将其与不禁用异常的函数进行比较,实际上我偶然发现了一个奇怪的情况,其中最好有异常不是。

我一直在使用 Matt Godbolt 的 Compiler Explorer 进行这些检查,并在没有任何标志的情况下在 x86-64 clang 12.0.1 上进行了检查(在 GCC 上这种奇怪的行为不存在)。

看看这个简单的代码:

auto* allocated_int()
{
    return new int{};
}

int main()
{
    delete allocated_int();

    return 0;
}

非常简单,几乎删除了从函数 allocated_int() 返回的分配指针。

正如预期的那样,二进制足迹也是最小的:

allocated_int():                     # @allocated_int()
        push    rbp
        mov     rbp, rsp
        mov     edi, 4
        call    operator new(unsigned long)
        mov     rcx, rax
        mov     rax, rcx
        mov     dword ptr [rcx], 0
        pop     rbp
        ret

另外,非常直接。 但是当我将 noexcept 关键字应用于 allocated_int() 函数时,二进制文件会膨胀。我将在此处应用生成的程序集:

allocated_int():                     # @allocated_int()
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     edi, 4
        call    operator new(unsigned long)
        mov     rcx, rax
        mov     qword ptr [rbp - 8], rcx        # 8-byte Spill
        jmp     .LBB0_1
.LBB0_1:
        mov     rcx, qword ptr [rbp - 8]        # 8-byte Reload
        mov     rax, rcx
        mov     dword ptr [rcx], 0
        add     rsp, 16
        pop     rbp
        ret
        mov     rdi, rax
        call    __clang_call_terminate
__clang_call_terminate:                 # @__clang_call_terminate
        push    rax
        call    __cxa_begin_catch
        call    std::terminate()

为什么 clang 为我们做这些额外的代码?除了调用 new(),我没有请求任何其他操作,我希望二进制文件能够反映这一点。

谢谢能解释的人!

Why is clang doing this extra code for us?

因为函数的行为不同。

I didn't request any other action but calling new()

通过声明函数 noexcept,您已请求调用 std::terminate 以防异常传播到函数之外。

allocated_int 在第一个程序中从不调用 std::terminate,而 allocated_int在第二个程序中可能会调用std::terminate。请注意,如果您记得启用优化器,添加的代码量会少得多。比较未优化的程序集大多是徒劳的。

您可以使用非抛出分配来防止:

return new(std::nothrow) int{};

这确实是一个敏锐的观察,即在非抛出函数中执行潜在的抛出操作可能会引入一些额外的工作,如果在潜在的抛出函数中执行相同的操作则不需要完成这些工作。

I've been trying to lookup the advantages of disabling exceptions on certain functions

使用非抛出的优点可能会在调用此类函数的地方实现;不在函数本身内。

没有 nothrow,您的函数只是作为您调用的分配函数的前端。它自己没有任何实际行为。事实上,在真正的可执行文件中,如果你进行 link 时间优化,它很有可能会完全消失。

当您添加 noexcept 时,您的代码会悄悄地转换成大致如下的内容:

auto* allocated_int()
{
    try { 
        return new int{};
    }
    catch(...) { 
        terminate();
    }
}

您看到生成的额外代码是捕获异常和调用 terminate when/if 所需的代码。