開発担当の橋本孔明です。
アプリケーションの開発環境は日々進歩を遂げ、新しいバージョンがリリースされていますが、すでに開発・リリースしているプロダクトについては、当時の開発環境のバージョンを維持してメンテナンスを続けることが多いものです。
Microsoft Visual Studioの場合、C#と.NET Frameworkでの開発であれば、仕様面ではVisual Studioそのものよりも.NET Framework SDKのバージョンへの依存度が高いため、それさえ変更しなければ影響は少ないのですが、C/C++での開発についてはコンパイラーのバージョンが製品依存であり、C99やC++11/14/17といった最新言語仕様対応による拡張が続く中、バージョン変更の影響が大きいといえるでしょう。
しかしながら、開発環境そのものが製品としてのサポートを終了してしまう場合、万一の開発環境起因の不具合や脆弱性などがあった場合の対応に問題がありますので、ある程度のタイミングで開発環境を更新していくといったことも必要になります(最近では、Microsoft Visual Studio 2008が来年サポートを終えるという発表が行われました)。
今回は、そんな開発環境の移行作業のなかで遭遇したトラブルを1件紹介します。
きっかけはVisual Studio 2012から2015への移行
先日、Visual Studio 2012を用いてネイティブC++およびWin32 APIで開発していた、とあるプロダクトの開発環境を、大規模バージョンアップに合わせてVisual Studio 2015に移行(2017も出たばかりですが、安定性を見極めるため2015を選択)しました。
移行作業自体は特に問題なく行うことができ、動作検証の結果でも特に問題は認められなかったのですが、「プログラム ファイル(EXEファイル)の大きさがおかしい」という指摘がありました。
当該のプログラムはそれほど大きな規模ではなく、元々は400KBほどだったのですが、これが1MB程度まで膨らんでしまっているというものでした。
まずはランタイム ライブラリ関係を疑ってみる
Visual Studio 2012から2015への移行でEXEファイルのサイズが変わる場合、どこが要因になるのかということを考えましたが、まっさきに思いつくのは標準C/C++仕様の拡張に伴うランタイム ライブラリ関係です。
まず、Visual Studio 2012・2015それぞれで新しいC++プロジェクトを作り、空のウィンドウを表示するだけの簡単なプログラムを実装してビルドしてみたのですが、生成されたEXEファイルにはせいぜい20KB程度の差しかありませんでした。これはおそらくランタイム ライブラリの拡張による純粋なコード サイズの増加分と思われますが、それにしても数百KBも増えたわけではないので、今回問題が発生したプロジェクトに固有の問題があるものと考えられます。
ランタイムに問題がないとすれば最適化処理の設定……?
さて、C++言語でプログラムを開発する場合、EXEファイルはリンカーによって作成されるわけですが、デバッグ ビルドならともかく、リリース ビルドでは基本的に不要となるコードやデータ(使われなかった関数や静的変数など)はリンク時に除外されます。
この不要データ除外の設定は、Visual Studioの2012や2015では、リンカー オプションの「最適化」ページにある「参照」と「COMDATの圧縮」という項目が該当します。
問題の発生していたプロジェクトでは、この設定が行われていない(未指定)状態となっていました。未指定状態の場合、リンカー オプションで「デバッグ情報の生成(/DEBUG)」が有効になっていると、両方とも「最適化オフ」の扱いとなることが、ドキュメントには明記されていますが、万一の不具合発生時、エラー レポートを元に不具合調査を行えるよう、デバッグ情報(プログラム データベース)は常に生成するようにしていました。
このため、リンク時に本来不要なコードやデータが削除されずにEXEファイルへと取り込まれてしまい、その結果ファイル サイズが肥大化してしまっていたのです。
実はVisual Studio 2012の時点でも同じ問題があったのですが、2012では実際のファイル サイズ増加がそれほど大きくなく(200KB程度)、それまで特に問題視もされていなかったため見落としてしまっていたのでした。2015でランタイム ライブラリのサイズ増加があった結果、リンクされてしまう本来不要なコード量も増加し、このファイル サイズになってしまったものと思われます。
「参照」と「COMDATの圧縮」のオプションは、2012や2015で新規にプロジェクトを作成した場合、デフォルトで有効化されています。今回のプロジェクトは息が長いプロダクトのもので、2012になる前、2005や2008といったさらに古い環境からずっと継承してきたものであり、それらのオプション指定の見直しを行っていなかったことが問題の遠因となっていたようです。
少々まとまりが無いトラブル報告となってしまいましたが、開発環境を移行する際には、言語仕様の変化だけではなく、コンパイラーやリンカーに関する動作オプションの見直しもきちんと行うべきである、という教訓が得られた一件でした。