Visual C++ 2015でコンパイルしたプログラムを試していたところ、Windows XPでプロセスが問答無用で死ぬ現象に見舞われました。いろいろ試してみた結果、およそ原因・回避方法が分かりましたので、ここに書き記します。

条件・症状

次の条件をすべて満たす場合に注意が必要です。

  • Visual C++ 2015でDLLをビルドする
  • そのDLLはLoadLibraryなどで実行時に読み込まれる
  • Windows XPで実行される

このとき、次の新機能のいずれも使ってはなりません。死にます。

  • thread_local
  • 関数内でのstatic変数

死ぬというのは、プロセスの強制終了のことです。

Windows XPでプログラムが強制終了したときの図

なお、関数内での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)がちょうどこの条件でダメだからです。

感想

Visual C++ 2015のthread_localと関数内static変数がWindows XPで使えない(場合がある)という話でした。ちょっと残念ですね。

thread_localが実装されるときには、__declspec(thread)の問題をどう回避するのだろうと気になっていました。TlsAllocなどを使う実装にするのかなと期待していたのですけど、ダメだったというわけです。そして、関数内staticも影響を受けるとは思いもよりませんでした。

とは言え、Visual C++ 2013より悪くなっていることはないので、これからもVisual C++ 2015を使い続けたいと思います。


スポンサード リンク

この記事のカテゴリ

  • ⇒ VC++ 2015で作ったDLLをWindows XPで動かすと最悪死ぬ