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

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

簡單範例

編輯

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

//header.hpp
...
//source.cpp
#include "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]

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

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

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

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

  • 編譯一個原始檔,至多只能使用一個預編譯頭。
  • 編譯原始檔時,如果先於預編譯頭對應的標頭檔編譯器就掃描到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防範的標頭檔)、生成並測試預編譯頭。

外部連結

編輯

參考文獻

編輯
  1. ^ MSDN Help Document: hdrstop. [2015-06-07]. (原始內容存檔於2018-09-24). 
  2. ^ GCC online documentation §3.20: Using Precompiled Headers. [2015-06-07]. (原始內容存檔於2015-09-18). 
  3. ^ GCC online documentation §3.2 Options Controlling the Kind of Output. [2015-06-07]. (原始內容存檔於2015-09-18).