C++ 静态变量单例模式的误会(线程安全)

         今天很low逼的发现,一直以来以为在visual studio 编译器中, static定义的函数内变量是线程安全的, 今天项目中的一个单例测试问题给了我一课,也给大家分享一下,避免像我这样半桶水的再掉坑里。代码是这么写的:
  1. class testA{
  2.     public:
  3.        static testA *getInstance(){
  4.               static testA obj;
  5.               return &obj;
  6.        }
  7. };
复制代码

这个单例代码已经忘了是哪位老前辈教我的了 , 网上看了一发,犯这个错误的人还是不少的。
先抛出结论:这个单例写法不是线程安全的。

测试中发现的问题, 于是要验证分析一波, 编译器到底是怎么处理这个写法的才导致了这个写法的非线程安全的结果, 做如下测试代码:
  1. class testA{
  2. public:
  3.          static testA *getInstance(){
  4.                   static testA obj;
  5.                   return &obj;
  6.          }

  7. public:
  8.          int a = 1;
  9. };

  10. int _tmain(int argc, _TCHAR* argv[])
  11. {
  12.           testA *p = testA::getInstance();         ================> 调试这里打上断点

  13.           p->a = 2;
  14.           return 0;
  15. }
复制代码
直接VS中运行起来,在断点处断下, 右键 - 转入反汇编模式,得如下编译器实现代码:

  1. testA::getInstance:
  2. push        ebp  
  3. mov         ebp,esp  
  4. sub         esp,0C0h  
  5. push        ebx  
  6. push        esi  
  7. push        edi  
  8. lea         edi,[ebp-0C0h]  
  9. mov         ecx,30h  
  10. mov         eax,0CCCCCCCCh  
  11. rep stos    dword ptr es:[edi]                         -------------------------------->以上代码忽略, 编译器防止堆栈溢出保护代码
  12. mov         eax,dword ptr ds:[00F2913Ch]          -----------------------> 这里表示静态数据区的一个标志位存储位置
  13. and         eax,1                                                   ----------------------->判断标志位是否置位
  14. jne         testA::getInstance+3Fh (0F2245Fh)     ----------------------->如果标志位设置了,则跳过静态变量初始化
  15. mov         eax,dword ptr ds:[00F2913Ch]           
  16. or          eax,1                                                      ----------------------->判断没有设置标志位,这里设置一把标志位
  17. mov         dword ptr ds:[00F2913Ch],eax           ------------------------>标志位保存起来
  18. mov         ecx,0F29138h                                     ------------------------>获取静态变量testA 的静态数据区地址,并作为this传给testA的构造(参考下一句)
  19. call        testA::testA (0F21127h)                       ------------------------> 执行构造
  20. mov         eax,0F29138h                                    ------------------------> 返回this指针
  21. pop         edi  
  22. pop         esi  
  23. pop         ebx  
  24. add         esp,0C0h  
  25. cmp         ebp,esp  
  26. call        __RTC_CheckEsp (0F21154h)  
  27. mov         esp,ebp  
  28. pop         ebp  
  29. ret  
复制代码

参看上面的几句注释代码发现, 编译器在静态变量的地址紧挨着的四字节位置设置了一个保护标志位,内存布局如下:
testA this
标志位 0/1

所以,多线程问题的引入就是这个标志位没有任何保护,所以导致 this 指针的这个位置也没有了保护,因此引入了 getInstance 的多线程问题

你可能感兴趣的:(C++,静态变量,单例,线程安全)