C++ 局所静的変数の初期化メカニズムとデコンパイル解析

Visual Studio 2017環境で実行したデコンパイル結果をもとに、C++における局所静的変数の初期化処理を分析する。 34: for (int i = 0; i < 5; i++) 0029734E C7 45 F8 00 00 00 00 mov dword ptr [ebp-8],0 00297355 EB 09 jmp main+30h (0297360h) 00297357 8B 45 F8 mov eax,dword ptr [ebp-8] 0029735A 83 C0 01 add eax,1 0029735D 89 45 F8 mov dword ptr [ebp-8],eax 00297360 83 7D F8 05 cmp dword ptr [ebp-8],5 00297364 7D 0E jge main+44h (0297374h) 35: { 36: ShowStatic(i); 00297366 8B 45 F8 mov eax,dword ptr [ebp-8] 00297369 50 push eax 0029736A E8 4B C4 FF FF call ShowStatic (02937BAh) 0029736F 83 C4 04 add esp,4 37: } 00297372 EB E3 jmp main+27h (0297357h) このループ構造では、変数 `i` が毎回インクリメントされ、関数 `ShowStatic` に渡される。重要なのは、**局所静的変数は一度だけ初期化される**という性質である。 void ShowStatic(int nValue) { static int local_count1 = nValue; static int local_count2 = nValue; printf("%d\r\n", local_count1); printf("%d\r\n", local_count2); } 関数内部の静的変数初期化処理は、以下の手順で行われる。 00297160 55 push ebp 00297161 8B EC mov ebp,esp 00297163 81 EC C0 00 00 00 sub esp,0C0h ... static int local_count1 = nValue; 0029717E A1 AC 8E 35 00 mov eax,dword ptr [_tls_index (0358EACh)] 00297183 64 8B 0D 2C 00 00 00 mov ecx,dword ptr fs:[2Ch] 0029718A 8B 14 81 mov edx,dword ptr [ecx+eax*4] 0029718D A1 68 8E 35 00 mov eax,dword ptr ds:[00358E68h] 00297192 3B 82 04 01 00 00 cmp eax,dword ptr [edx+104h] 00297198 7E 2B jle ShowStatic+65h (02971C5h) // 初期化済みか判定 0029719A 68 68 8E 35 00 push 358E68h 0029719F E8 43 A8 FF FF call __Init_thread_header (02919E7h) 002971A4 83 C4 04 add esp,4 002971A7 83 3D 68 8E 35 00 FF cmp dword ptr ds:[358E68h],0FFFFFFFFh 002971AE 75 15 jne ShowStatic+65h (02971C5h) // 実際の初期化 002971B0 8B 45 08 mov eax,dword ptr [nValue] 002971B3 A3 64 8E 35 00 mov dword ptr [local_count1 (0358E64h)],eax 002971B8 68 68 8E 35 00 push 358E68h 002971BD E8 3A B1 FF FF call __Init_thread_footer (02922FCh) 002971C2 83 C4 04 add esp,4 各静的変数の初期化には、**スレッドセーフな制御機構**が用いられている。具体的には、以下のような仕組み: - 各スレッドごとのローカルストレージ(TLS)に初期化状態フラグを保持。 - 初期化開始前に `__Init_thread_header` を呼び出し、他のスレッドからの競合を防止。 - 初期化完了後、`__Init_thread_footer` を呼び出して状態を更新し、待機中のスレッドを起床させる。 これらの関数は、`_Init_thread_header` と `_Init_thread_footer` の二つから成り立つ。共通して利用される同期機構は、`CRITICAL_SECTION` と `CONDITION_VARIABLE`(または旧バージョンのイベント)による排他制御。 extern "C" void __cdecl _Init_thread_header(int* const pOnce) noexcept { _Init_thread_lock(); if (*pOnce == Uninitialized) { *pOnce = BeingInitialized; } else { while (*pOnce == BeingInitialized) { _Init_thread_wait(XpTimeout); if (*pOnce == Uninitialized) { *pOnce = BeingInitialized; _Init_thread_unlock(); return; } } _Init_thread_epoch = _Init_global_epoch; } _Init_thread_unlock(); } extern "C" void __cdecl _Init_thread_footer(int* const pOnce) noexcept { _Init_thread_lock(); ++_Init_global_epoch; *pOnce = _Init_global_epoch; _Init_thread_epoch = _Init_global_epoch; _Init_thread_unlock(); _Init_thread_notify(); } この実装により、複数スレッドからの同時アクセスにおいても、**静的変数の初期化は一回のみ**に保証される。初期化中に例外が発生した場合でも、`_Init_thread_abort` が状態をリセットし、他のスレッドが再試行できるようになっている。 これらのコードは Visual Studio 2017 の CRT 機能として、`vc\tools\msvc\14.16.27023\crt\src\vcruntime\thread_safe_statics.cpp` に実装されている。

タグ: static variable thread safety C++ runtime tls initialization

8月29日 11:31 投稿