預編譯頭
沒有或很少條目連入本條目。 (2015年6月18日) |
預編譯頭(precompiled header)是程式設計時把標頭檔編譯為中間格式(如目標文件),以節約在開發過程中編譯器反覆編譯該標頭檔的開銷。 C語言、C++語言、Objective C語言等都有類似的技術。
有的標頭檔包含了巨量的原始碼(如著名的windows.h
),或者使用模板編程時要生成巨大的標頭檔模板庫(如Eigen math library與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]
- 編譯器選項/Fp中給出的參數;
- 原始檔中預處理器指令
#pragma hdrstop [( "filename" )]
給出的預編譯頭的檔名。原始檔從開始之處至該行#pragma hdrstop
的內容,用於建立預編譯頭(若使用/Yc
選項)或者被預編譯頭取代(若使用/Yu
選項)。 /Yu"头文件基名.h"
中的檔案的base name加上字尾.PCH;- 當前原始檔的的base name加上字尾.PCH。
GCC
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防範的標頭檔)、生成並測試預編譯頭。
外部連結
- The Care and Feeding of Pre-Compiled Headers (頁面存檔備份,存於互聯網檔案館)
- Precompiled Headers with GCC
- Fractal eXtreme: Precompiled headers (頁面存檔備份,存於互聯網檔案館)
參考文獻
- ^ MSDN Help Document: hdrstop. [2015-06-07]. (原始內容存檔於2018-09-24).
- ^ GCC online documentation §3.20: Using Precompiled Headers. [2015-06-07]. (原始內容存檔於2015-09-18).
- ^ GCC online documentation §3.2 Options Controlling the Kind of Output. [2015-06-07]. (原始內容存檔於2015-09-18).