静态变量初始化与线程安全
前⾔
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小时内删除。
发表评论