Visual C++ 2015でコンパイルしたプログラムを試していたところ、Windows XPでプロセスが問答無用で死ぬ現象に見舞われました。いろいろ試してみた結果、およそ原因・回避方法が分かりましたので、ここに書き記します。
条件・症状
次の条件をすべて満たす場合に注意が必要です。
- Visual C++ 2015でDLLをビルドする
- そのDLLはLoadLibraryなどで実行時に読み込まれる
- Windows XPで実行される
このとき、次の新機能のいずれも使ってはなりません。死にます。
thread_local
- 関数内でのstatic変数
死ぬというのは、プロセスの強制終了のことです。
なお、関数内でのstatic変数のVC++ 2015の新機能とは、初期化処理が複数スレッド対応(コンパイラが適切に排他制御するコードを出力する)になるという、C++11仕様への対応です。参考: static変数初期化とスレッドセーフ – yohhoyの日記
回避策
どっちもVisual C++ 2015の新機能ですので、使わなければ大丈夫です。Visual C++ 2013までで書いてきたソースコードが急にダメになるわけではありません。
thread_local
は使わない。TlsAlloc系のWindows API関数を使う。- 関数内でのstatic変数を使うなら、
/Zc:threadSafeInit-
コンパイルオプションを用い、VC++ 2013までの挙動に戻す。複数スレッドから実行されうる場合、std::call_once (cpprefjp)など別の手段で排他制御を行う。
試したソースコード
というわけで、それを試すのに使ったソースコード、DLL側2つとそれを呼び出すEXE側のものを順に掲載します。いずれも、DLL版ランタイム (/MD)と静的リンク版 (/MT)のどちらでも同じようにダメです。
thread_localを使うDLL
// cl /MD /EHsc /LD t.cpp #include <iostream> #include <string> thread_local std::string s; extern "C" __declspec(dllexport) void f() { s = "123"; std::cout << s << std::endl; } |
関数内staticを使うDLL
// cl /MD /EHsc /LD s.cpp #include <iostream> #include <string> extern "C" __declspec(dllexport) void f() { static std::string s = "123"; std::cout << s << std::endl; } |
DLLを読み込んで実行するEXE
これ書いてから気付いたのですが、rundll32.exeで実行できるDLLにしておけば、EXE側を自分で書く必要はありませんでしたね。
// cl /MD /EHsc test.cpp /link /SUBSYSTEM:CONSOLE,5.01 #include <windows.h> int main() { auto hmod = LoadLibraryW(L"t.dll"); // またはs.dll auto pfn = reinterpret_cast<void (*)()>(GetProcAddress(hmod, "f")); pfn(); } |
実行時に読み込まれるDLLでのTLS
thread_localがダメなのは、実のところ意外な話ではありません。Visual C++に昔からあった__declspec(thread)
がちょうどこの条件でダメだからです。
- KB118816: [SDK32] LoadLibrary() は _declspec(thread) でエラーとなる
- MSDN ライブラリ > 開発ツールと言語ドキュメント > Visual Studio 2015 へようこそ > Visual C++ > C++ 言語および標準ライブラリ > Microsoft 固有の修飾子 > __declspec > スレッドにもこんな記述があります。
XP システムでは、DLL が __declspec(thread) のデータを使用して、データが LoadLibrary 経由で動的に読み込まれる場合、thread は正常に機能しないことがあります。
感想
Visual C++ 2015のthread_localと関数内static変数がWindows XPで使えない(場合がある)という話でした。ちょっと残念ですね。
thread_localが実装されるときには、__declspec(thread)
の問題をどう回避するのだろうと気になっていました。TlsAllocなどを使う実装にするのかなと期待していたのですけど、ダメだったというわけです。そして、関数内staticも影響を受けるとは思いもよりませんでした。
とは言え、Visual C++ 2013より悪くなっていることはないので、これからもVisual C++ 2015を使い続けたいと思います。
スポンサード リンク |
この記事のカテゴリ
- VC++ ⇒ VC++ 2015で作ったDLLをWindows XPで動かすと最悪死ぬ