为什么本地静态对象的初始化使用隐藏的保护标志?

Leu*_*nko 8 c++ optimization x86-64

C++ 中的本地静态对象在第一次需要时初始化一次(如果初始化有副作用,这是相关的):

void once() {
    static bool b = [] {
        std::cout << "hello" << std::endl; return true;
    } ();
}
Run Code Online (Sandbox Code Playgroud)

once 将在第一次调用时打印“hello”,但如果再次调用则不会打印。

我已经将这种模式的一些变体放入Compiler Explorer并注意到所有知名实现(GCC、Clang、ICC、VS)本质上都在做同样的事情:guard variable for once()::b创建一个隐藏变量,并检查是否“这次”需要初始化主变量;如果是,它会被初始化,然后设置守卫,下次它不会跳出初始化代码。例如(通过将 lambda 替换为对 的调用来最小化extern bool init_b();):

once():
        movzx   eax, BYTE PTR guard variable for once()::b[rip]
        test    al, al
        je      .L16
        ret
.L16:
        push    rbx
        mov     edi, OFFSET FLAT:guard variable for once()::b
        call    __cxa_guard_acquire
        test    eax, eax
        jne     .L17
        pop     rbx
        ret
.L17:
        call    init_b()
        pop     rbx
        mov     edi, OFFSET FLAT:guard variable for once()::b
        jmp     __cxa_guard_release
        mov     rbx, rax
        mov     edi, OFFSET FLAT:guard variable for once()::b
        call    __cxa_guard_abort
        mov     rdi, rbx
        call    _Unwind_Resume
Run Code Online (Sandbox Code Playgroud)

...来自带有 -O3 的 GCC 6.3。

这并非不合理,而且我知道在实践中,当条件一致时,条件跳转无论如何都接近免费。然而,我的直觉仍然会被执行这一联合国有条件地跳转到初始化代码,这为最后一个动作将覆盖与发信跳转nop指令。不一定是每个平台上的选项,但 x86 系列似乎对您可以读取或写入的内容以及位置非常自由。

这个看似简单的想法没有主流编译器使用它有什么问题?(或者我只需要在我的例子中更加努力?)

use*_*198 3

在大多数现代操作系统上,修改程序加载的代码会导致问题。这既可能导致性能问题(未经修改的代码可以在某些系统上的 dll 的许多实例之间共享页面),也可能导致安全问题(阻止使用可执行空间保护技术)。