前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >说一说c++ static变量----log4cxx也会导致程序Crash?

说一说c++ static变量----log4cxx也会导致程序Crash?

作者头像
河边一枝柳
发布2021-08-06 14:59:51
7470
发布2021-08-06 14:59:51
举报

1. 问题背景

log4cxx是C++常用的log库。

在项目中碰到程序启动后偶尔很快就crash,查看函数调用栈后,居然在log4cxx的模块。对于常用的开源库,笔者一般还是比较放心的,于是目光一直聚焦在产品的代码,搜寻无果后,只能去看看一看log4cxx的源码了,果不其然,最终寻得是log4cxx的一个多线程bug所致,而这个bug和C++函数内的static变量是否线程安全有关。环境相关信息如下:

  • 编译器: VS2005
  • log4cxx当时最新版本是0.10.0

项目中会调用到log4cxx的getWarn这个接口,如下代码所示,由于这个函数存在非线程安全的问题,导致程序Crash。为了更好的描述问题,博主下一节采用一个简单的例子去做分析:为什么这个是非线程安全的。

代码语言:javascript
复制
LevelPtr Level::getWarn() {
    static LevelPtr level(new Level(Level::WARN_INT, LOG4CXX_STR("WARN"), 4));
    return level;
}

2. 通过样例代码分析问题

这里们写了一段样例代码,模拟Log4cxx上述代码。采用VS2005编译,为了避免程序被优化,博主采用的是Debug模式编译。

代码语言:javascript
复制
class TestObject
{
public:
    int m_iVal;
    TestObject()
    {
        m_iVal = 4;
    }
};

TestObject TestFunction()
{
    static TestObject obj;
    return obj;
}

以上代码简单来说,就是返回一个TestObject的类对象。TestFunction中永远返回一个静态对象obj。

代码语言:javascript
复制
TestObject TestFunction()
{
0000000140001800  mov         qword ptr [rsp+8],rcx 
0000000140001805  push        rdi  
0000000140001806  sub         rsp,30h 
000000014000180A  mov         rdi,rsp 
000000014000180D  mov         rcx,0Ch 
0000000140001817  mov         eax,0CCCCCCCCh 
000000014000181C  rep stos    dword ptr [rdi] 
000000014000181E  mov         rcx,qword ptr [rsp+40h] 
0000000140001823  mov         qword ptr [rsp+20h],0FFFFFFFFFFFFFFFEh 
    static TestObject obj;
//===========================
这个地方从内存中读取一个值,可以理解为编译器给程序自动加了一个变量bInit
(判断obj对象是否初始化了,bInit初始值为0)
(1)将bInit读取到eax,然后判断为1表示已经初始化,则直接返回对象;
(2)如果为0,则按顺序继续执行。
//===========================
000000014000182C  mov         eax,dword ptr [$S1 (14000F2A4h)] 
0000000140001832  and         eax,1 
0000000140001835  test        eax,eax 
0000000140001837  jne         TestFunction+55h (140001855h) 
//===========================
将bInit值设置为1, 并且调用obj构造函数, 完成对象初始化
//===========================
0000000140001839  mov         eax,dword ptr [$S1 (14000F2A4h)] 
000000014000183F  or          eax,1 
0000000140001842  mov         dword ptr [$S1 (14000F2A4h)],eax 
0000000140001848  lea         rcx,[obj (14000F2A0h)] 
000000014000184F  call        TestObject::TestObject (1400011EFh) 
0000000140001854  nop              
    return obj;
0000000140001855  mov         rax,qword ptr [rsp+40h] 
000000014000185A  mov         ecx,dword ptr [obj (14000F2A0h)] 
0000000140001860  mov         dword ptr [rax],ecx 
0000000140001862  mov         rax,qword ptr [rsp+40h] 
}

看了以上汇编和解释之后,大家应该能明白这里存在一个Race Condition。当多个线程,同时调用TestFunction这个函数,当线程A刚执行完0000000140001842 mov dword ptr [$S1 (14000F2A4h)],eax (第28行), 线程B刚好进入TestFunction执行,认为obj已经初始化了,则直接返回对象,而此时对象内部的m_iVal还未进入构造函数内赋值为4, 将会使用错误的值继续执行代码,并非程序的本意。

3. C++ 11 线程安全

博主采用了VS2015 (支持C++ 11)编译了以上的代码,得到如下汇编, 其通过_Init_thread_header和_Init_thread_footer来保证局部的静态对象的初始化线程安全。

代码语言:javascript
复制
TestObject TestFunction()
{
00007FF65F411830  mov         qword ptr [rsp+8],rcx  
00007FF65F411835  push        rbp  
00007FF65F411836  push        rdi  
00007FF65F411837  sub         rsp,108h  
00007FF65F41183E  lea         rbp,[rsp+20h]  
00007FF65F411843  mov         rdi,rsp  
00007FF65F411846  mov         ecx,42h  
00007FF65F41184B  mov         eax,0CCCCCCCCh  
00007FF65F411850  rep stos    dword ptr [rdi]  
00007FF65F411852  mov         rcx,qword ptr [rsp+128h]  
00007FF65F41185A  mov         qword ptr [rbp+0C8h],0FFFFFFFFFFFFFFFEh  
    static TestObject obj;
00007FF65F411865  mov         eax,104h  
00007FF65F41186A  mov         eax,eax  
00007FF65F41186C  mov         ecx,dword ptr [_tls_index (07FF65F41C1E0h)]  
00007FF65F411872  mov         rdx,qword ptr gs:[58h]  
00007FF65F41187B  mov         rcx,qword ptr [rdx+rcx*8]  
00007FF65F41187F  mov         eax,dword ptr [rax+rcx]  
00007FF65F411882  cmp         dword ptr [obj+4h (07FF65F41C180h)],eax  
00007FF65F411888  jle         TestFunction+88h (07FF65F4118B8h)  
00007FF65F41188A  lea         rcx,[obj+4h (07FF65F41C180h)]  
00007FF65F411891  call        _Init_thread_header (07FF65F41101Eh)  
00007FF65F411896  cmp         dword ptr [obj+4h (07FF65F41C180h)],0FFFFFFFFh  
00007FF65F41189D  jne         TestFunction+88h (07FF65F4118B8h)  
00007FF65F41189F  lea         rcx,[obj (07FF65F41C17Ch)]  
00007FF65F4118A6  call        TestObject::TestObject (07FF65F411028h)  
00007FF65F4118AB  nop  
00007FF65F4118AC  lea         rcx,[obj+4h (07FF65F41C180h)]  
00007FF65F4118B3  call        _Init_thread_footer (07FF65F411078h)  
    return obj;
00007FF65F4118B8  mov         rax,qword ptr [rbp+100h]  
00007FF65F4118BF  mov         ecx,dword ptr [obj (07FF65F41C17Ch)]  
00007FF65F4118C5  mov         dword ptr [rax],ecx  
00007FF65F4118C7  mov         rax,qword ptr [rbp+100h]  
}
00007FF65F4118CE  lea         rsp,[rbp+0E8h]  
00007FF65F4118D5  pop         rdi  
00007FF65F4118D6  pop         rbp  
00007FF65F4118D7  ret

这个功能在VS2015中默认开启,如果想要禁用这个功能, 可以添加额外的编译选项/Zc:threadSafeInit-。详细的可以参考/Zc:threadSafeInit (Thread-safe Local Static Initialization)。

4. 总结

1. C++ 11之前函数内部static变量非线程安全。

2. 尽量在条件允许的情况下,将编译器升级到支持C++ 11。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一个程序员的修炼之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档