VC++ のプリコンパイル済みヘッダ

VC++ で久しぶりに作業してて,コンパイルの時間に腹が立ったのでプリコンパイル済みヘッダ(pch)を使おうと思ったけど,はまったのでメモ.

はじめにプロジェクトの作成のときに「プリコンパイル済みヘッダを使用する」か何かのチェックボックスをチェックしてたなら必要ない知識ですが,いつも私は「空のプロジェクト」テンプレートで作業するのでたいてい自前です.というか pch は滅多使いません.

Google コーディング規約には「#include "stdafx.h" はソースに書かずにコンパイラのオプションでインクルードせよ」とあるのでそれにも対応します.

まず,ファイルの準備.どこかに,

  • stdafx.h とか stdafx.hxx とか適当な名前で, pch を使うためのヘッダ(以下 pch ヘッダ)を準備する.
  • stdafx.cpp とか stdafx.cxx とか適当な名前で pch を作るためのソース(以下 pch ソース)を準備する.

stdafx という名前にしておくと,あ,pch なんだなとわかっていいです.

pch を使う実際のビルドはこういう流れです:

  1. stdafx.cpp をコンパイル → stdafx.obj が生成される
  2. stdafx.obj をもとに pch を作成 → .pch が生成される
  3. 作成された pch を使ってその他ソースをコンパイル,オブジェクト作成
  4. オブジェクトをリンク

実際にはコンパイラが 1 と 2 の手順をまとめてやってくれますが,何がいいたいかといいますと,pch ソースと,他のソースではコンパイルの仕方が違うということです.私がはまったのはこれが原因です.

コンパイルオプションの設定です.ビルドの順序と逆順に設定しますが,特に意味は無いです.

まず,ソリューションエクスプローラからプロジェクトのアイコンを右クリックしてプロジェクトのプロパティを開きます.プロジェクトに含まれる普通のソースファイルは pch を使用する側です.ですから,pch に関する設定はこのようになります:

プリコンパイル済みヘッダーの作成/使用:プリコンパイル済みヘッダー ファイルを使用する(/Yu)
ファイルを使用して PCH を作成/使用: [pch ヘッダのパス]
プリコンパイル済みヘッダーファイル: [ディフォルトのままでかまいません]

さらに,pch ヘッダを自動的にインクルードするよう,「必ずインクルード」のところに pch ヘッダのパスを追加しますが,「ファイルを使用して PCH を作成/使用」と「必ずインクルード」の値はおなじにしなければなりません.ディレクトリツリーやファイルの配置が若干複雑になっていても「pch ヘッダのパス」をうまく設定してやることで切り抜けられます.

次に, pch を作るために pch ソースに特化したコンパイルオプションが必要です.ソリューションエクスプローラから pch ソースのアイコンを右クリックして pch ソースのプロパティを開きます.pch ソースをもとに pch を作成するので設定はこうなります:

プリコンパイル済みヘッダーの作成/使用:プリコンパイル済みヘッダーを作成する (/Yc)
ファイルを使用して PCH を作成/使用: [pch ヘッダのパス]
プリコンパイル済みヘッダーファイル: [ディフォルトのままでかまいません]

当たり前ですが,「プリコンパイル済みヘッダーファイル」の値は pch ソースと他のソースで一致していなければなりません.また,pch ソースの設定における「ファイルを使用して PCH を作成/使用」の値と, pch ソースに書き込む #include プリプロセッサディレクティヴの引数とがおなじ値でなければなりません.つまり,

ファイルを使用して PCH を作成/使用: hoge/stdafx.hxx

と設定したなら,pch ソースには完全に同じ値を指定して,

#include "hoge/stdafx.hxx"

と書かなければなりません.たとえ pch ソースをコンパイルするときの inlcude のサーチパスに pch ヘッダが存在していても,です.ただ,pch ソースの設定における「ファイルを使用して PCH を作成/使用」の値とプロジェクトの設定における「ファイルを使用して PCH を作成/使用」の値とが異なっていても次の手順をこなせば何の問題もありません.とはいえ,全て統一しておくべきだとは思います.

これで大方設定は済んだのですが,「ファイルを使用して PCH を作成/使用」の値と,プロジェクトの設定における「必ずインクルード」でインクルードする pch ヘッダの名前とが異なっている場合にコンパイルに失敗します.pch ソースのプリプロセス時に余計なヘッダをインクルードすることになるからです.解決策としては, pch ソースのプロパティの「必ずインクルード」の値を $(NOINHERIT) にしてプロジェクトの値を継承しないよう設定します.「親またはプロジェクトの規定値から継承」のチェックを外せばそうなります.ただ,私は pch ソースからインクルードすべきは pch ヘッダだけであるのにいろいろヘッダを持ち込まれるのは気分が悪いのでこの設定を行っています.

ここまで設定し終わったならば, pch ソースを右クリックして表示されたメニューから「コンパイル」を選んでコンパイルしてみましょう.コンパイルが成功したならば出力先ディレクトリに .pch ファイルが出力されているはずです. .pch の生成には若干時間が掛かります(状況によります)が,あとのコンパイルではすでにコンパイルされた型情報を使用できますからコンパイル時間を短縮できます.ただし, pch ヘッダから頻繁に更新されるヘッダをインクルードしては意味がありません(更新のたびに pch を生成することになるので).C の標準ライブラリ, STL や Boost など外部のライブラリにとどめておくべきです.

追記
「たとえ pch ソースをコンパイルするときの inlcude のサーチパスに pch ヘッダが存在していても」
というのは, pch ヘッダと pch ソースの間に,

#include "stdafx.hxx"

と pch ソースに記述して正しくインクルードできるような,ファイルパスの関係があったとしても,ということです.

カテゴリー: プログラミング パーマリンク

コメントを残す