静态变量初始化与线程安全
前⾔
c++11 担保了 static 变量的初始化线程安全。但是⽼的c++标准并没有担保,所以说⽼版本的编译器可能static 变量初始化在多线程的条件下会造成问题
c++ 98/03 关于静态初始化标准
下⾯是⽼版本标准对这个问题的描述,简⾔⽽之它只是担保了local static 变量的初始化发⽣于当该表达式第⼀次执⾏时。
Here’s an excerpt from section 6.7 of a working draft (N1095) of the current C++ standard (C++98)
The zero-initialization (8.5) of all local objects with static storage duration (3.7.1) is performed before any other
initialization takes place. A local object of POD type (3.9) with static storage duration initialized with constant-
expressions is initialized before its block is first entered. An implementation is permitted to perform early initialization of other local objects with static storage duration under the same conditions that an implementation is permitted to statically initialize an object with static storage duration in namespace scope (3.6.2). Otherwise such an object is initialized the first time control passes through its declaration; such an object is considered initialized upon the
completion of its initialization.
⼤多数编译器选择 在 全局作⽤域内的static 变量会在进⼊main函数前初始化,⽽ func内的local static 变量只会在该函数第⼀次被调⽤的时候初始化。这就造成了⼀个问题,可能函数内部的static local 变量的初始化不是线程安全的,所以我们不能假设 static local 变量的初始化就是线程安全的。
编译器的实现
demo
#include <iostream>
using namespace std;
class Foo {
public:
Foo(const char* s = "") {
cerr << "Constructing Foo with " << s << endl;
}
};
void somefunc()
{
static Foo funcstatic("funcstatic");
Foo funcauto("funcauto");
}
static Foo glob("global");
int main()
{
cerr << "Entering main\n";
somefunc();
somefunc();
somefunc();
return 0;
}
vs2008 实现
现在看看somefunc 这个 local static 变量初始化函数的实现。
static Foo funcstatic("funcstatic");
00E314FD  mov        eax,dword ptr [$S1 (0E3A148h)]
00E31502and        eax,1
00E31505  jne        somefunc+71h (0E31531h)
00E31507  mov        eax,dword ptr [$S1 (0E3A148h)]
00E3150C or          eax,1
00E3150F  mov        dword ptr [$S1 (0E3A148h)],eax
00E31514  mov        dword ptr [ebp-4],0
00E3151B  push        offset string "funcstatic"(0E3890Ch)
00E31520  mov        ecx,offset funcstatic (0E3A14Ch)
00E31525  call        Foo::Foo (0E31177h)
00E3152A  mov        dword ptr [ebp-4],0FFFFFFFFh
Foo funcauto("funcauto");
00E31531  push        offset string "funcauto"(0E38900h)
00E31536  lea        ecx,[ebp-11h]
00E31539  call        Foo::Foo (0E31177h)
从上⾯汇编看到编译器对于是否初始化只是简单的通过⼀个计数器的判断如下⾯汇编代码。那么就有可能出现俩个线程得到 0E3A148h 这个地址的值都为0,那么这个时候就会发⽣初始化执⾏俩次。这个实现也符合 ⽼版本 c++ 标准,所以没问题
00E314FD  mov        eax,dword ptr [$S1 (0E3A148h)]
00E31502and        eax,1
00E31505  jne        somefunc+71h (0E31531h)
gcc 实现
gcc 4.0 版本以上的实现,使⽤相同的代码 使⽤ g++ -O0 -g 编译。从下⾯的汇编可以看到 gcc 使⽤了
guard_acquire/release 担保了它的线程安全。
gcc 可以使⽤-fno-threadsafe-statics 这个选项来关闭对 static local 变量多余的线程安全的开销调⽤。如果关闭了⽣成的代码和
vs2008差不多。另⼀⽅⾯说,这段代码引⼊了⼀些不可移植的问题。这段代码跑在gcc上编译就没问题,使⽤ vs2008就不⾏。
0000000000400a9d<_Z8somefuncv>:
400a9d:55                      push  rbp
400a9e:4889 e5                mov    rbp,rsp
400aa1:4883 ec 40            sub    rsp,0x40
400aa5:  b8 a8 216000          mov    eax,0x6021a8
400aaa:0f b6 00                movzx  eax,BYTE PTR [rax]
static修饰的变量400aad:84 c0                  test  al,al
400aaf:7576                  jne    400b27<_Z8somefuncv+0x8a>
400ab1:  bf a8 216000          mov    edi,0x6021a8
400ab6:  e8 cd fd ff ff          call  400888<__cxa_guard_acquire@plt> 400abb:85 c0                  test  eax,eax
400abd:0f95 c0                setne  al
400ac0:84 c0                  test  al,al
400ac2:7463                  je    400b27<_Z8somefuncv+0x8a>
400ac4:  c6 45 df 00            mov    BYTE PTR [rbp-0x21],0x0
400ac8:  be aa 0c4000          mov    esi,0x400caa
400acd:  bf b0 216000          mov    edi,0x6021b0
400ad2:  e8 89000000          call  400b60<_ZN3FooC1EPKc>
400ad7:  c6 45 df 01            mov    BYTE PTR [rbp-0x21],0x1
400adb:  bf a8 216000          mov    edi,0x6021a8
400ae0:  e8 03 fe ff ff          call  4008e8<__cxa_guard_release@plt> 400ae5:  eb 40                  jmp    400b27<_Z8somefuncv+0x8a>
400ae7:488945 c8            mov    QWORD PTR [rbp-0x38],rax
400aeb:488955 d0            mov    QWORD PTR [rbp-0x30],rdx
400aef:8b45 d0                mov    eax,DWORD PTR [rbp-0x30]
400af2:8945 ec                mov    DWORD PTR [rbp-0x14],eax
400af5:488b45 c8            mov    rax,QWORD PTR [rbp-0x38]
400af9:488945 e0            mov    QWORD PTR [rbp-0x20],rax
400afd:0f b6 45 df            movzx  eax,BYTE PTR [rbp-0x21]
400b01:83 f0 01                xor    eax,0x1
400b04:84 c0                  test  al,al
400b06:740a                  je    400b12<_Z8somefuncv+0x75>
400b08:  bf a8 216000          mov    edi,0x6021a8
400b0d:  e8 06 fe ff ff          call  400918<__cxa_guard_abort@plt> 400b12:488b45 e0            mov    rax,QWORD PTR [rbp-0x20]
400b16:488945 c8            mov    QWORD PTR [rbp-0x38],rax
400b1a:486345 ec            movsxd rax,DWORD PTR [rbp-0x14]
400b1e:488b7d c8            mov    rdi,QWORD PTR [rbp-0x38]
400b22:  e8 11 fe ff ff          call  400938<_Unwind_Resume@plt> 400b27:488d7d ff            lea    rdi,[rbp-0x1]
400b2b:  be b5 0c4000          mov    esi,0x400cb5
400b30:  e8 2b000000          call  400b60<_ZN3FooC1EPKc>
400b35:  c9                      leave
400b36:  c3                      ret
总结
使⽤⽼版本编译器编译c++代码,还是遵循标准不要做任何假设。参考

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。