StdAfx.h に関する考察

インテル® DPC++/C++ コンパイラー

この記事は、インテル® デベロッパー・ゾーンに公開されている「StdAfx.h」(https://software.intel.com/en-us/blogs/2015/04/17/stdafxh) の日本語参考訳です。


この記事は、Visual Studio* 環境下で C++ プロジェクトをコンパイルする開発者向けに記載されています。不慣れな環境ではすべてが奇妙で複雑に見え、初心者は特に stdafx.h に起因する奇妙なエラーにイラつくことがあります。それらはかなり多くの確率で、すべてのプロジェクトのプリコンパイル済みヘッダーを無効にすることで解決できます。この記事は、Visual Studio* を使用する初心者向けに stdafx.h にまつわる問題の解決を提示します。

プリコンパイル済みヘッダーの目的

プリコンパイル済みヘッダーはプロジェクトのビルド時間を短縮することを目的としています。Visual C++* を初めて使用し、小規模なプロジェクトをビルドする場合、プリコンパイル済みヘッダーを利用してもパフォーマンスの利点はありません。プリコンパイル済みヘッダーを有効にしても無効にしても、コンパイル時間は変わらないでしょう。これは利用者を混乱させる原因の 1 つです: 利用者はこのオプションの利点を感じられず、特定の業務でのみ有効であろうと結論付け必要でないと判断します。この考えは数年継続することもあります。

実際は、プリコンパイル済みヘッダーは非常に有用な技術です。おそらく数十のファイルで構成されるプロジェクトでは、その利点に気づくことができます。boost のような重いライブラリーを使用すると、パフォーマンスの向上はより顕著になります。

プロジェクトを構成する .cpp ファイルを調べると、それらの多くは <vector>、<string>、<algorithm> などの同じヘッダーをインクルードしていることが分かるでしょう。これらのヘッダーは、他のヘッダーを内部でインクルードすることがあります。

その結果、コンパイラーのプリプロセッサーは同じことを何度も繰り返し行い、同じファイルを何度も読み込んで、お互いに挿入し合い、#ifdef を処理しマクロを展開する必要があります。そのため、同じ操作を膨大な回数繰り返します。

プリプロセッサーの作業量をプロジェクトのコンパイル時に大幅に軽減できます。アイディアは、ファイルのグループを事前にプリプロセスし、必要に応じて作成済みのテキストを挿入します。

これは実際にはいくつかのステップが含まれます: 単純なテキストの代わりに、より高度な処理を行った情報を保存することができます。私は、それらのすべてが Visual C++* で実装されているか確認していませんが、例えば、すでに単語単位に分割したテキストを保存できることを知っています。そして、多くの場合コンパイル処理をスピードアップします。

プリコンパイル済みヘッダーの動作

プリコンパイル済みヘッダーファイルは、”.pch” という拡張子を持ちます。ファイル名は通常プロジェクト名と同一ですが、変更することもできます。*.pch ファイルは、展開されるヘッダー数に依存しますが、かなり大きくなる可能性があります。PVS-Studio の場合、およそ 3M バイトでした。

stdafx.cpp ファイルのコンパイル結果として *.pch ファイルが生成されます。このファイルは、コンパイラーにプリコンパイル済みヘッダーを作成することを指示するため、”/Yc” オプションを使用してビルドされます。stdafx.cpp ファイルは次の 1 行を含んでいます: #include “stdafx.h”。

主要な情報は “stdafx.cpp” ファイルに保存されます。プリコンパイルされるすべてのヘッダーファイルは、このファイルに含まれるべきです。例えば、以下は PVS-Studio で使用する stdafx.cpp ファイルです (テキストは記事に掲載するため一部省略されています):

#include "VivaCore/VivaPortSupport.h"
//For /Wall
#pragma warning(push)
#pragma warning(disable : 4820)
#pragma warning(disable : 4619)
#pragma warning(disable : 4548)
#pragma warning(disable : 4668)
#pragma warning(disable : 4365)
#pragma warning(disable : 4710)
#pragma warning(disable : 4371)
#pragma warning(disable : 4826)
#pragma warning(disable : 4061)
#pragma warning(disable : 4640)
#include <stdio.h>
#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <set>
#include <map>
#include <list>
#include <deque>
#include <memory>
#pragma warning(pop) //For /Wall

“#pragma warning” 宣言子は、標準ライブラリーに警告されるメッセージを排除するために必要です。

“stdafx.h” は、すべての .c と .cpp ファイルでインクルードされるべきです。また、それらのファイルから、すでに “stdafx.h” にインクルードされたヘッダーを取り除かなければいけません。

しかし、異なるファイルが類似するヘッダーの組み合わせをインクルードする場合はどうすればいいでしょう?
次に例を示します:

  • ファイル A: <vector>, <string>
  • ファイル B: <vector>, <algorithm>
  • ファイル C: <string>, <algorithm>

個別にプリコンパイル済みヘッダーを作成する必要はありますか?
そうすることも可能ですが、実際はその必要はありません。

<vector>、<string> そして <algorithm> が展開される 1 つのプリコンパイル済みヘッダーを作成するだけで済みます。多くのファイルを読み込んで、互いに挿入し合う必要がないプリプロセッサーの利点は、追加コードのフラグメントの構文解析の損失を上回ります。

プリコンパイル済みヘッダーの利用法

新たなプロジェクトを作成する場合、Visual Studio* のウィザードが 2 つのファイルを作成します: stdafx.h と stdafx.cpp。このファイルを通して、プリコンパイル済みヘッダーのメカニズムが実装されています。

これらのファイルは他のファイル名にすることができます: プロジェクト設定で指定したコンパイル・パラメーターの名称を指定します。

.c や .cpp ファイルは、単一のプリコンパイル済みヘッダーに使用できます。しかし、プロジェクトはいくつかの異なるプリコンパイル済みヘッダーを含んでいるかもしれません。ここでは、1 つしかないと仮定します。

ウィザードを使用している場合、stdafx.h と stdafx.cpp ファイルはすで既に生成され、コンパイルに必要なすべてのオプションは定義されています。

プロジェクトでプリコンパイル済みヘッダーを使用するオプションが有効になっていない場合、有効にする方法を見つけましょう。次の手順を推奨します:

  1. すべての .cpp ファイル向けに [すべてのオプション] で [プリコンパイル済みヘッダー] を有効にします。プロジェクトのプロパティーから、[構成プロパティー] > [C/C++] > [プリコンパイル済み] を選択します:
    • [プリコンパイル済み] オプションで “使用 (/Yu)” を選択します。
    • [プリコンパイル済みヘッダーファイル] オプションに “stdafx.h” を設定します。
    • [プリコンパイル済みヘッダー出力ファイル] オプションに “$(IntDir)$(TargetName).pch” を設定します。
  2. stdafx.h ファイルを作成し、プロジェクトに追加します。事前に前処理したいヘッダーをこのファイルにインクルードします。
  3. stdafx.cpp ファイルを作成し、プロジェクトに追加します。このファイルは次の 1 行のみを含みます: #include “stdafx.h”。
  4. [すべてのオプション] で stdafx.h 向けに設定を変更: [プリコンパイル済みヘッダー] オプションに “作成 (/Yc)” を設定します。

これで、プリコンパイル済みヘッダーのオプションを有効にできました。コンパイルを実行すると、コンパイラーは *.pch ファイルを作成します。ただし、エラーの発生によりコンパイルは終了するでしょう。

すべての *.c と *.cpp ファイルがプリコンパイル済みヘッダーを使用するように設定しましたが、それでは十分ではありません。各ファイルに #include “stdafx.h” を追加する必要があります。

“stdafx.h” ヘッダーは、各 *.c や *.cpp ファイルにインクルードする最初のファイルでなければいけません。

これは必須です! そうしないと、多くのコンパイルエラーが発生するでしょう。

その理由を考えてみる意味はあります。”stdafx.h ” ファイルが最初にインクルードされていると、すでに前処理されたテキストをファイルに置き換えることができます。このテキストは、常に同じ状態でいずれにも影響を受けません。

では、”stdafx.h” の前に他のいくつかのファイルをインクルードし、それらのファイルには #define bool char が含まれていると考えてください。 そして、”bool” として記述されたすべてのファイルのコンテンツを変更すると、状況はすべて未定義になるでしょう。そして、前処理されたテキストを挿入できないため、”プリコンパイル済みヘッダー” 全体の構成が破壊されます。これが、”stdafx.h” をファイルの先頭でインクルードしなければならない理由の 1 つであると考えられます。おそらく、他にも原因があります。

ライフハック

すべての .c と .cpp ファイルに手動で #include “stdafx.h” を入力するのは、かなり面倒で退屈な作業です。また、多くのファイルが変更されたことでバージョン管理システムから新しいリビジョンを取得するでしょう。これは、あまり良い傾向ではありません。

ソースファイルとしてプロジェクトにインクルードされたサードパーティー製のライブラリーには、問題を引き起こす原因となるものもあります。これらのファイルを変更しても意味がありません。最善の解決策は、それら向けにプリコンパイル済みヘッダーを無効にすることですが、多くの小規模なライブラリーを使用する場合には不便です。そのため、開発者は常にプリコンパイル済みヘッダーに関連した問題に遭遇するでしょう。

しかし、プリコンパイル済みヘッダーを扱う簡単な方法があります。この手法は万能ではありませんが、これまで多くのケースで私を助けてきました。

すべてのファイルに手動で #include “stdafx.h” を追加する代わりに、”必ず使用されるインクルード・ファイル” オプションを使用します。

プロジェクトのプロパティーから [C/C++] > [詳細設定] を開きます。[必ず使用されるインクルード・ファイル] に次のテキストを設定:

StdAfx.h;%(ForcedIncludeFiles)

これで、”stdafx.h” はコンパイル時にすべてのファイルに自動的にインクルードされます。これは、手動で各ファイルの先頭に #include “stdafx.h” を追加せずに、すべての .c や .cpp ファイルにコンパイラーが自動的に挿入するという利点があります。

stdafx.h で何をインクルードするか

これは非常に重要です。何も考えずに “stdafx.h” にそれぞれのヘッダーをインクルードすると、スピードアップどころかコンパイル速度が遅くなります。

“stdafx.h” にインクルードするすべてのファイルは、その内容に依存します。”stdafx.h” が “X.h” をインクルードすると考えてください。”X.h” にわずかな変更を加えても、プロジェクト全体をすべて再コンパイルしなければならないことがあります。

重要なルール

“stdafx.h” には、変更されることのない、または、まれにしか変更されないファイルのみをインクルードします。最適な候補として、システムやサードパーティーから提供されるヘッダーファイルが挙げられます。

独自のプロジェクト・ファイルを “stdafx.h” にインクルードする際には、十分な注意を払ってください。その場合、非常にまれにしか変更されないファイルのみをインクルードします。

月に 1 度程度変更される “*.h” ファイルは、候補としては頻度が高すぎるでしょう。多くの場合、.h ファイルのすべての必要な変更を終えるには複数回の修正が必要です (通常 2 から 3 回)。プロジェクト全体を 2 から 3 回再コンパイルするのは、妥当ですか? また、プロジェクトにかかわるすべてのメンバーが、同じことを行う必要があります。

しかし、変更されていないファイルに気を留める必要はありません。真に必要なファイルのみをインクルードしてください。2 ~ 3 のファイルだけを必要とするのであれば、それらのセットをインクルードするのは推奨しません。代わりに、必要な場所で単純にインクルードします。

複数のプリコンパイル済みヘッダー

1 つのプロジェクト内に、複数のプリコンパイル済みヘッダーを必要とすることはありますか? 確かにありますが、それはまれな状況でしょう。いくつかの例を紹介します。

プロジェクトが、.c と .cpp の両方を使用していると考えてください。この場合、単一の .pch ファイルを共有することはできません。コンパイルエラーとなります。

.c と .cpp ごとに .pch ファイルを作成する必要があります。C ファイル (xx.c) をコンパイルした後に、C++ ファイル (yy.cpp) をコンパイルします。そのため、プロジェクトの設定で、C ファイル向けのプリコンパイル済みヘッダーと別の C++ ファイル向けのプリコンパイル済みヘッダーを指定する必要があります。

注意: これら 2 つの .pch ファイルの名称は異なるものにします。さもないと、一方が上書きされてしまいます。

また、別のソリューションもあります。プロジェクトの一部で大きなライブラリーを使用し、他の部分でもまた別の大きなライブラリーを使用する場合、当然のことですが、プロジェクトの異なる部分では、お互いのライブラリーには関知しません。つまり、異なるライブラリー内のエンティティー名には重複がある可能性があります。

この場合、2 つのプリコンパイル済みヘッダーを作成し、プログラムの異なる部分で使用することが論理的です。すでに述べたように、生成される .pch ファイルには任意の名称を指定することができます。また、生成された .pch ファイルの名前を変更することもできます。すべては慎重に行われるべきですが、2 つのプリコンパイル済みヘッダーを使用するのに特に難しいことはありません。

プリコンパイル済みヘッダーを使用する際のよくある過ち

これまでの記事を注意深く読んでいただくことで、stdafx.h に関連するエラーを理解して排除できるでしょう。しかし、慣れていないプログラマーの方は、よくある過ちを確認し、その理由を確認することを推奨します。習うより慣れろです。

Fatal error C1083: プリコンパイル済みヘッダー ファイルを開けません。 ‘Debug\project.pch’: No such file or directory

存在しない .pch ファイルをプリコンパイル済みヘッダーとして利用しようとしています。次のような原因があります:

  1. stdafx.cpp ファイルがコンパイルされていません。そのため、.pch ファイルは生成されていません。これは、最初にソリューションをクリーンアップし、その後 1 つの .cpp ファイルをコンパイルしようとすると (Ctrl-F7)、発生する可能性があります。この問題を解決するには、ソリューション全体もしくは最初に stdafx.cpp ファイルをコンパイルします。
  2. .pch ファイルを生成するコンパイルオプションが指定されていない可能性があります。これは、/Yc コンパイルオプションを追加することで解決できます。この問題は、Visual Studio* に慣れていないプログラマーが、プロジェクトで初めてプリコンパイル済みヘッダーを使用する際によくあるものです。 この問題の対処法は、前述の “プリコンパイル済みヘッダーの利用法” の節をご覧ください。

Fatal error C1010: プリコンパイル ヘッダーを検索中に不明な EOF が見つかりました。’#include “stdafx.h”‘ をソースに追加しましたか?

ファイルは /Yu オプションでコンパイルされました。プリコンパイル済みヘッダーを使用するように指示されていますが、”stdafx.h” がファイルでインクルードされていません。

ファイルに #include “stdafx.h” を追加する必要があります。

そうできないようであれば、この .c もしくは .cpp ファイルでプリコンパイル済みヘッダーを使用しないでください。/Yu コンパイルオプションを削除します。

Fatal error C1853: プリコンパイル ヘッダー ファイルが旧バージョンのコンパイラで作成されています。また、C++ のプリコンパイル済みヘッダー ファイルを C で使用しています (その逆も考えられます)。

プロジェクトは、C (.c) と C++ (.cpp) ファイルの両方で構成されています。.c と .cpp で共有するプリコンパイル済みヘッダー (.pch ファイル) を使用することはできません。

解決方法:

  1. すべての C ファイルでプリコンパイル済みヘッダーの利用を無効にします。.c ファイルは .cpp よりもプリコンパイルが高速に処理されます。数個の .c ファイルを使用するならば、プリコンパイル済みヘッダーを無効にしてもコンパイル時間が大幅に長くなることはないでしょう。
  2. 2 つのプリコンパイル済みヘッダーを作成します。最初は、stdafx_cpp.cpp と stdafx_cpp.h から生成し、次に stdafx_c.c と stdafx_c.h から生成します。したがって、.c と .cpp ファイルには異なるプリコンパイル済みヘッダーを使用する必要があります。当然のこととして、.pch ファイルの名称は異なったものになります。

プリコンパイル済みヘッダーを使用するとコンパイラーが誤動作する

このような場合、何か誤ったことをしている可能性があります。例えば、#include “stdafx.h” がファイルの先頭にないような場合です。

次ににその例を示します:

int A = 10;
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[]) {
  return A;
}

このコードは、奇妙なメッセージを出力してコンパイルに失敗するでしょう:

error C2065: 'A' : 宣言されていない識別子

これは、#include “stdafx.h” (この行を含む) 以前のすべてのテキストが、プリコンパイル済みヘッダーであると考えられます。このファイルをコンパイルすると、コンパイラーは #include “stdafx.h” 以前のテキストを .pch ファイルで置き換えようとします。その結果、”int A = 10;” の行は失われます。

以下に正しいコードを示します:

#include "stdafx.h"
int A = 10;
int _tmain(int argc, _TCHAR* argv[]) {
  return A;
}

もう一つの例:

#include "my.h"
#include "stdafx.h"

ファイル “my.h” の内容は使用されません。その結果、このファイル内で宣言される関数を使用することができません。このような振る舞いは、プログラマーに混乱をもたらします。彼らは、対策としてプリコンパイル済みヘッダーを無効にし、Visual C++* には問題があると感じるようになります。コンパイラーはバグの少ないツールであるということを忘れないでください。99.99% の場合、それはコンパイラーの問題ではなく、皆さんのコードに問題があります (裏付け)。

この問題に対処するには、常にファイルの先頭に #include “stdafx.h” を追加することを確実にします。もちろん、コメント文はコンパイルされることがないため、#include “stdafx.h” の前に記述することができます。

別の方法は、”必ず使用されるインクルード・ファイル” オプションを使用することです。”ライフハック” の節をご覧ください。

プリコンパイル済みヘッダーを使用する場合、常にプロジェクト全体が再コンパイルされる

定期的に編集するファイルを stdafx.h にインクルードしています。または、誤って自動生成されたファイルがインクルードされている可能性があります。

注意深く “stdafx.h” の内容を調査します: 変更されない、もしくはほとんど変更されないヘッダーのみをインクルードする必要があります。あるインクルード・ファイルは変更されませんが、それらは他の .h ファイルを参照してる可能性があることを忘れないでください。

奇妙なこと

エラーを修正したにもかかわらず、問題に遭遇することがあります。デバッガーが奇妙な振る舞いを報告します。

この問題は .pch ファイルに関連しています。何らかの理由で、コンパイラーはヘッダーファイルのいずれかが変更されていることを知ることができません。そのため、.pch ファイルを再コンパイルせず以前のテキストを挿入し続けます。これは、ファイルの変更時間に関連するいくつかの障害に起因する可能性があります。

これは、非常にまれな状況ですが、可能性があるいことを知っておく必要があります。個人的に、私はこれまで 2 ~ 3 回この問題を経験しました。これは、プロジェクト全体を再コンパイルすることで解決できます。

プリコンパイル済みヘッダーを使用したプロジェクトが、PVS-Studio や CppCat で解析できない

これは、私たちのサポートサービスに最も問い合わせの多い問題です。詳細については、以下のドキュメントをご覧ください: “PVS-Studio: トラブルシューティング (http://www.viva64.com/en/d/0304/)”。この問題の概要を説明します。

ソリューションが正しくコンパイルできても、それが正しく実装されているとは限りません。1 つのソリューションが、複数のプロジェクトを含む場合があります。これぞれは、独自のプリコンパイル済みヘッダーを使用します(stdafx.h と stdafx.cpp ファイル)。

あるプロジェクト内のファイルから、別のプロジェクトのファイルを起動したときに問題が生じます。これは便利な手法であり、よく利用されます。しかし、開発者は .cpp ファイルが #include “stdafx.h” を含んでいるのを忘れています。

この場合、どの stdafx.h ファイルが取り込まれるでしょう? プログラムが上手くコンパイルできたなら、それはただラッキーだっただけに過ぎません。

残念ながら、.pch ファイルを使用した場合、それを再現するのは非常に困難です。皆さんは、プリプロセッサーがまったく異なった動作をすることを知るでしょう。

一時的にプリコンパイル済みヘッダーを無効にすることで、ソリューションが誤った方法で実装されているかどうかを確認できます。状況によっては、プロジェクトが正しくコンパイルできるかどうか疑問を持つほどのエラーが生じるかもしれません。

詳細は、ドキュメントを参照してください。不明な点がありましたら、サポートサービスまでお問い合わせください。

まとめ

プリコンパイル済みヘッダーを使用した作業は非常に簡単です。この機能を利用し、常に “コンパイラーの多数のバグ” に直面する場合、そのメカニズムにある作業の原則を理解していないだけかもしれません。この記事が、皆さんの誤解を解くことに役立つことを願っています。

プリコンパイル済みヘッダーは、大幅にプロジェクトのコンパイル時間を改善できる非常に便利な機能です。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

タイトルとURLをコピーしました