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` に実装されている。