C++ 函数局部静态变量是否自动引发分支?

C++ 函数局部静态变量是否自动引发分支?,c++,C++,例如: int foo() { static int i = 0; return i++; } 变量i仅在第一次调用foo时初始化为0。这是否自动意味着其中有一个隐藏的分支来防止初始化多次发生?或者有更聪明的技巧来避免这种情况吗?是的,它必须产生一个分支,并且它还必须至少产生一个原子操作以实现安全的并发初始化。该标准要求以并发安全的方式在函数项上初始化它们 只有证明lazy init与输入main()之前的一些早期初始化之间的差异是等效的,实现才能避开此要求。例如,对于从常量初

例如:

int foo()
{
    static int i = 0;
    return i++;
}

变量
i
仅在第一次调用
foo
时初始化为
0
。这是否自动意味着其中有一个隐藏的分支来防止初始化多次发生?或者有更聪明的技巧来避免这种情况吗?

是的,它必须产生一个分支,并且它还必须至少产生一个原子操作以实现安全的并发初始化。该标准要求以并发安全的方式在函数项上初始化它们


只有证明lazy init与输入main()之前的一些早期初始化之间的差异是等效的,实现才能避开此要求。例如,对于从常量初始化的简单POD,编译器可能会选择像文件作用域全局一样更早地初始化它,因为它是不可观察的,并保存惰性初始化代码,但这是一个不可观察的优化。

是的,有一个分支。每次输入函数时,代码必须检查变量是否已初始化。但正如下面将解释的,您通常不必关心这个分支

例子 查看此代码:

#include <iostream>

struct Foo { Foo(){ std::cout << "FOO" << std::endl;} };
void foo(){ static Foo foo; }
int main(){ foo();}
A你看,有一个
jne
!然后,使用
\uucxa\uguard\u acquire
获得一个保护,然后使用
je
。因此,编译器似乎正在生成此处著名的

每个编译器都会生成一个分支吗? 我很确定规范没有要求必须使用分支或双重检查锁定。它只是要求初始化必须是线程安全的。但是,我看不到在没有分支的情况下执行线程安全初始化的方法。因此,即使规范没有强制要求它,在当前的CPU体系结构中也不可能省略这个分支

这树枝贵吗? 考虑您是否应该关心该分支机构: 您绝对不应该关心这个分支,因为它将被正确预测(因为一旦对象初始化,分支总是采用相同的路径)。因此,分支几乎是免费的。为了优化目的而避免使用静态局部变量永远不会产生任何可观察到的性能好处

真的没有办法绕过那家分行吗? 如果构造函数是不可观察的,比如简单地用常量值初始化,那么它可能会在程序启动时急切地执行,而忽略分支。然而,如果它是可观察的,那么事情就会变得相当棘手:

我看到的唯一可能性是R.Martinho Fernandes的回答(已删除):代码可以自我修改。即,初始化完成后,只需删除初始化代码。然而,由于以下原因,这一想法是不切实际的:

  • 自修改代码很难实现线程安全
  • 通常,带有内存标记的可执行文件是写保护的,所以不允许代码重写自身
  • 这是不值得的,因为分支并不昂贵(见上文)

  • @cameron的可能重复问题是问什么时候,这个问题是问如何。对于我的编译器在快速测试中,这没有生成任何代码在运行时初始化i,它将变量放置在内存位置,并从可执行文件加载值。尽管编译器必须像在第一次调用它时初始化该值一样工作,但如果没有明显的副作用,它实际上不必这样做。如果它是一个没有构造函数的基本类型,它可能会避免整个开销。如果没有错的话,@ JCODER与CysExPR的现代C++可以在编译时对Mor复杂对象进行初始化。有效防止这种情况发生的是初始化代码中与运行时相关的数据,可能是任何不能声明为constexpr的数据。虽然展示一个程序集示例是有益的,但它本身并不是一个确定的答案。我不赞成将编译器特定的程序集结果作为证据,而不是引用实际的语言规则。@小狗张贴GCC确实等于说“通常”,这在实践中非常有用。有趣的是,C保证比C++更有效,因为静态局部变量只能用C中的编译时常数初始化,它们可能只使用文件范围变量。C的效率并不高,但一点也不高;如果静态局部在C中是有效的,那么C++的实现被允许早期地初始化它(实际上在使用精确的相同的机制,即<代码>数据> /代码>段)。@ EcMutur-是的,允许的,但是你不能依赖它。@ FrdodoFLUT:在C.没有比你更大的能力了。
    _Z3foov:
    .LFB974:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    .cfi_lsda 0x3,.LLSDA974
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    pushq   %r12
    pushq   %rbx
    .cfi_offset 12, -24
    .cfi_offset 3, -32
    movl    $_ZGVZ3foovE3foo, %eax
    movzbl  (%rax), %eax
    testb   %al, %al
    jne .L7                     <------------------- FIRST CHECK
    movl    $_ZGVZ3foovE3foo, %edi
    call    __cxa_guard_acquire <------------------- LOCK    
    testl   %eax, %eax
    setne   %al
    testb   %al, %al
    je  .L7                     <------------------- SECOND CHECK
    movl    $0, %r12d
    movl    $_ZZ3foovE3foo, %edi