预编译头

**預編譯頭**（precompiled header）是程式設計時把標頭檔編譯為中間格式（如目的檔），以節約在開發過程中編譯器反覆編譯該標頭檔的開銷。 C 語言、C++ 語言、Objective C 語言等都有類似的技術。

有的標頭檔包含了巨量的原始碼（如著名的 `windows.h`），或者使用模板編程時要生成巨大的標頭檔模板庫（如 Eigen math library 與 Boost C++ libraries)。為減少編譯時間，某些編譯器允許把標頭檔編譯為某種中間形式稱為預編譯頭（precompiled header），後續再編譯原始檔時就可以儘量直接使用這些預編譯頭。

## 簡單範例

C++ 檔案 source.cpp 包括 header.hpp：

首次編譯 source.cpp 時，編譯器生成 `header.pch` 的預編譯頭。以後再編譯該程式時，編譯器會比較該標頭檔的時間戳，如果標頭檔沒有改變，編譯器直接使用預編譯頭。

## Visual Studio

Microsoft Visual Studio 採取一個標頭檔（預設命名為 **stdafx.h**），其內容是整個軟體專案中被各個編譯單元所使用的原始碼，並極少修改。每個編譯單元的原始碼從最開始之處直至 `#include "stdafx.h"` 的內容完全相同。其中一個編譯單元（原始檔預設命名為 "stdafx.cpp"）以 `#include "stdafx.h"` 為最後一行（不考慮程式注釋），並用編譯選項 `/Yc"stdafx.h"` 編譯，這將生成預編譯頭（預設命名為 "<專案名>.pch"）, 包含了到 `#include "stdafx.h"` 這一行為止的所有代碼編譯結果。

專案中的其他編譯單元，使用 `/Yu"stdafx.h"` 編譯，這將把從開頭直至 `#include "stdafx.h"` 這一行的原始碼跳過編譯過程，直接用預編譯頭（預設命名為 "<專案名>.pch"）代替。

stdafx 中的 AFX 代表 Application Framework eXtensions。AFX 是 Microsoft Foundation Classes (MFC) 的舊稱。編程者也可以用其他名字的標頭檔代替 stdafx.h。

Visual Studio 編譯器確定預編譯頭的檔名，依照下述次序：

1. 編譯器選項 / Fp 中給出的參數；
2. 原始檔中預處理器指令 `#pragma hdrstop [( "filename" )]` 給出的預編譯頭的檔名。原始檔從開始之處至該行 `#pragma hdrstop` 的內容，用於建立預編譯頭（若使用 `/Yc` 選項）或者被預編譯頭取代（若使用 `/Yu` 選項）。
3. `/Yu"头文件基名.h"` 中的檔案的 base name 加上字尾.PCH；
4. 當前原始檔的的 base name 加上字尾.PCH。

## GCC

GCC3.4 版開始支援預編譯頭。當編譯器輸入檔案為標頭檔時（例如 sample.h），產生預編譯頭，其檔案名稱是原來的標頭檔追加 ".gch"字尾（如 sample.h.gch）。當 gcc 編譯原始檔時，對於遇到的每個標頭檔，都在 INCLUDE 路徑中的每個目錄下，先搜尋追加".gch" 字尾的預編譯頭，如果沒有再搜尋該標頭檔。如果找到預編譯頭且滿足一些條件，則直接使用該預編譯頭；否則就把該標頭檔在原始檔中展開並編譯之。 可以在編譯原始檔時使用-H 選項，如果顯示的預編譯頭的全路徑這一行是以 "!" 開始，說明編譯器成功使用了預編譯頭；如果這一行以 "x" 開始，說明編譯器找到了這一預編譯頭但未能使用它。

gcc 對 C 語言的標頭檔與 C++ 的標頭檔產生的格式不同，不能混用。如果標頭檔的名字不是 ".h"或".hpp" 字尾結尾（C++ 標準庫的標頭檔基本如此），可以用編譯選項-x c++-header 讓 gcc 把當前的輸入檔案當作 C++ 標頭檔來處理。用編譯選項-x c-header 讓 gcc 把當前的輸入檔案當作 C 標頭檔來處理。

使用預編譯頭時必須滿足下列條件：

* 編譯一個原始檔，至多只能使用一個預編譯頭。
* 編譯原始檔時，如果先於預編譯頭對應的標頭檔編譯器就掃描到 C/C++ 的詞（token），則不能使用該預編譯頭。也就是說，在預編譯頭對應的標頭檔之前的原始檔中，只能有預處理指令（preprocessor directive)。 不能在另一個標頭檔中引入使用預編譯頭。
* 預編譯頭與原始檔必須是相同語言。不能在編譯 C++ 原始檔時使用 C 語言的預編譯頭。
* 預編譯頭與原始檔應當由同一個編譯器編譯。以保證二進位相容。
* 在預編譯頭之前定義的宏（macro）或者被引入源程式時與預編譯頭生產時完全一致，或者預編譯頭沒有用到該宏。這包括了由編譯選項 - D 定義的宏、#define 定義的宏、以及某些編譯選項如 - O 或 - Wdeprecated 隱式定義的宏。
* 原始檔用 - g 選項編譯以取得除錯資訊時，其使用的預編譯頭也應該是用 - g 編譯選項生成的。但是，用 - g 選項編譯生成的預編譯頭，也可用在沒有除錯資訊輸出的原始檔編譯。
* 生成與使用預編譯頭，必須使用相同的 - m 選項。該編譯選項給出目標硬體平台的相關資訊。
* 生成與使用預編譯頭，必須使用相同的 - fexceptions 選項。
* 生成與使用預編譯頭，必須使用以 - f, -p, -O 開頭的相同的命令列選項。目前還不知道哪些編譯選項可以安全地改變或者決不能改變，最安全的選擇是生成與使用預編譯頭使用相同的編譯選項。已知下述選項可以安全地改變：

```
 -fmessage-length=  -fpreprocessed  -fsched-interblock -fsched-spec  
 -fsched-spec-load  -fsched-spec-load-dangerous 
 -fsched-verbose=number  -fschedule-insns  -fvisibility= 
 -pedantic-errors
```

上述條件如果不被滿足，編譯器將自動使用標頭檔而不是對應的預編譯頭。

## C++Builder

在專案預設組態中，C++Builder 編譯器無保留地生成在 `#pragma hdrstop` 之前所有標頭檔的預編譯頭。如果可能的話，預編譯頭會被專案的所有模組共享。例如，當使用 Visual Component Library 時，通常首先會包含 `vcl.h` 標頭檔，該標頭檔含有大多數經常被用到的 VCL 標頭檔。因此，預編譯頭被全專案共享可以顯著減少生成時間。

另外，C++Builder 能設定為使用一個特定的標頭檔作為預編譯頭，和 Visual C++ 提供的機制類似。

C++Builder 2009 引入了「Precompiled Header Wizard（預編譯頭精靈）」來為被包含的標頭檔提取所有原始碼，然後自動為特定的檔案分類（例如排除那些不是專案成員或者沒有 Include 防範的標頭檔）、生成並測試預編譯頭。

## 外部連結

* The Care and Feeding of Pre-Compiled Headers （頁面存檔備份，存於網際網路檔案館）
* Precompiled Headers with GCC
* Fractal eXtreme: Precompiled headers （頁面存檔備份，存於網際網路檔案館）

## 參考文獻