跳至內容

預編譯頭

本頁使用了標題或全文手工轉換
維基百科,自由的百科全書

預編譯頭(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。

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防範的頭文件)、生成並測試預編譯頭。

外部連結

參考文獻

  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).