C/C++プログラミングの教訓----関数内の静的クラスオブジェクトの非スレッドセキュリティの初期化(C++11以前)
9309 ワード
多くのプログラマーは、プログラムを作成する際に、関数内の静的(
問題の背景
我々の製品ではlog 4 cxxに対していくつかの簡単なパッケージ(VS 2005コンパイルを採用)を行い、
例
ここでは,
以上のコードは、単純に
非スレッドセキュリティの解析
この問題を分析するには、VSの逆アセンブリで確認しなければなりません.私は以下のコードに注釈を付けてこの問題を直接説明します.
以上のアセンブリと解釈を見て、ここにRace Conditionがあることがわかります.複数のスレッドが同時に
C++11スレッドセキュリティ
ブロガーはVS 2015(C++11対応)を用いて以上のコードをコンパイルし、
この機能はVS 2015でデフォルトでオンになっており、この機能を無効にするには、追加のコンパイルオプション
まとめ C++11の前に、関数内の静的オブジェクトの使用をできるだけ避けます. できるだけ条件が許す限り、コンパイラをC++11対応のVS 2015以上にアップグレードしましょう.
static
)変数を使用し、関数内のこの変数が一定の情報を永続的に記録し、アクセス範囲の制御を関数内に限定することができることを満たすことができます.しかし、関数内の静的クラスオブジェクトの初期化は非スレッドで安全です.問題の背景
我々の製品ではlog 4 cxxに対していくつかの簡単なパッケージ(VS 2005コンパイルを採用)を行い、
getWarn
というインタフェースに呼び出されます.この関数には非スレッドセキュリティの問題があるため、プログラムCrashが発生します.問題をよりよく説明するために、ブロガーは後で簡単な例を採用して分析します.なぜこれは非スレッドで安全なのか.LevelPtr Level::getWarn() {
static LevelPtr level(new Level(Level::WARN_INT, LOG4CXX_STR("WARN"), 4));
return level;
}
例
ここでは,
VS2005
を用いたサンプルコードを書き,プログラムが最適化されないようにDebug
モードコンパイルを用いた.class TestObject
{
public:
int m_iVal;
TestObject()
{
m_iVal = 4;
}
};
TestObject TestFunction()
{
static TestObject obj;
return obj;
}
以上のコードは、単純に
TestObject
のクラスオブジェクトを返します.TestFunction
には、静的オブジェクトobj
が永遠に返されます.では、今ポイントが来ました.2つのことを知らなければなりません.1.OBjは、関数TestFunction
が初めて呼び出すときにコンストラクション関数2を呼び出す.obj
アプリケーションが起動すると、obj
のオブジェクトメモリの値は0
になります.さらに、ここでのobj
は、初期化時(ここでは、コンストラクション関数を呼び出すと考えられる)に非スレッドで安全である.非スレッドセキュリティの解析
この問題を分析するには、VSの逆アセンブリで確認しなければなりません.私は以下のコードに注釈を付けてこの問題を直接説明します.
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), bInit eax, 1 , ; 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
を実行し終わると、スレッドBはTestFunction
に入って実行され、objが初期化されたと思ってオブジェクトに直接戻り、実際にはこのときオブジェクト内部のm_iVal
は0であり、プログラマの本意ではない.C++11スレッドセキュリティ
ブロガーはVS 2015(C++11対応)を用いて以上のコードをコンパイルし、
_Init_thread_header
と_Init_thread_footer
により局所的な静的オブジェクトの初期化スレッドの安全を保証するアセンブリを得た.具体的にGoogleを実現するには見つからず、興味のある学生はまとめて研究することができます.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
この機能はVS 2015でデフォルトでオンになっており、この機能を無効にするには、追加のコンパイルオプション
/Zc:threadSafeInit-
を追加することができます.詳細は/Zc:threadSafeInit(Thread-safe Local Static Initialization)を参照してください.まとめ