函数局部静态变量是否自动引发分支?
Does a function local static variable automatically incur a branch?
例如:
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();}
现在,这是gcc4.8为foo
函数生成的汇编代码的第一部分:
_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
A你看,有一个jne
!然后,使用__cxa_guard_acquire
和je
获得一个守卫。因此,编译器似乎在这里生成了著名的双重检查锁定模式。
每个编译器都会生成一个分支吗?
我很确定规范没有强制要求必须使用分支或双重检查锁定。它只是要求初始化必须是线程安全的。但是,我没有看到没有分支就可以执行线程安全初始化的方法。因此,即使规范没有强制这样做,在当前的CPU体系结构中也不可能省略这里的分支。
分支贵吗?
考虑是否应该关心这个分支:你绝对不应该关心这个分支,因为它将被正确地预测(因为一旦对象初始化,分支总是采用相同的路由)。因此,该分支几乎是自由的。为了优化目的而避免使用静态局部变量永远不会产生任何可观察到的性能优势。
真的没有办法绕过这个分支吗?
如果构造函数不是可观察的,比如简单地用常量值初始化,那么它可以在程序启动时立即执行,而忽略分支。然而,如果它是可观察的,那么事情就变得相当棘手了:
我看到的唯一可能性是R. Martinho Fernandes的回答(已被删除):代码可以自我修改。也就是说,一旦初始化完成,只需删除初始化代码即可。然而,这个想法是不切实际的,原因如下:
- 自修改代码很难获得线程安全。
- 通常,内存标记的可执行文件是写保护的,所以代码不允许重写自己。这是不值得的,因为分支并不昂贵(见上文)。
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗
- #为""定义宏;静态";针对不同的上下文
- cmake如何在fedora工作站中找到boost静态库包
- 静态数据成员的问题-修复链接错误会导致编译器错误
- 将公共但非静态的成员函数与ALGLIB集成
- cmake在我的项目中所需的所有静态库都不成功
- C++从另一个类访问公共静态向量的正确方法是什么
- IPC使用多个管道和分支进程来运行Python程序
- 基于boost的程序的静态链接——zlib问题
- 在静态库中嵌入类方法
- Qt C++静态thread_local QNetworkAccessManager是线程应用程序的好选择吗
- 如何在C++中获得"静态纯虚拟"功能?
- 私有类型的静态常量成员
- 使用gcc从静态链接的文件中查找可选符号
- 在 .h 文件中的类中声明静态变量和在.cpp文件中声明"global"变量有什么区别
- 如何在C++中使用非静态成员函数作为回调函数
- 将静态库链接到不带-fPIC的共享库中
- 静态结构和一个定义规则
- 为什么虚函数不能是静态的和全局的?
- 函数局部静态变量是否自动引发分支?