閱讀英文

共用方式為


先行編譯標頭檔

當您在Visual Studio中建立新專案時,名為 pch.h 的先行編譯頭檔會新增至專案。 (在 Visual Studio 2017 和更早版本中,檔案稱為 stdafx.h。)檔案的目的是加快建置程式。 此處應包含任何穩定的標頭檔,例如標準連結庫標頭, 例如 <vector>。 只有在先行編譯標頭或其包含的任何檔案經過修改時,才會進行編譯。 如果您只對專案原始碼進行變更,則組建會略過先行編譯標頭的編譯。

先行編譯標頭的編譯程式選項為 /Y。 在專案屬性頁中,選項位於 [組態屬性>C/C++>Precompiled Headers] 底下。 您可以選擇不使用先行編譯標頭,而且您可以指定標頭檔名稱和輸出檔的名稱和路徑。

自訂先行編譯的程序代碼

對於需要大量時間來建置的大型專案,您可能想要考慮建立自定義先行編譯的檔案。 Microsoft C 和 C++ 編譯器提供對任何 C 或 C++ 程式碼進行先行編譯的選項,包括內嵌程式碼。 使用此效能功能,您可以編譯穩定的程式代碼主體、將程式代碼的編譯狀態儲存在檔案中,並在後續編譯期間,將先行編譯的程式代碼與仍在開發中的程式代碼結合。 因為穩定程序代碼不需要重新編譯,因此每個後續的編譯速度都比較快。

先行編譯原始程式碼的時機

先行編譯的程式代碼在開發周期期間很有用,以減少編譯時間,特別是:

  • 您一律會使用大量不常變更的程式代碼主體。

  • 您的程式包含多個模組,這些模組全都使用一組標準 include 檔案和相同的編譯選項。 在此情況下,所有 Include 檔案都可以先行編譯成一個先行編譯的標頭。 如需處理包含檔案之較新方式的詳細資訊,請參閱 比較標頭單位、模組和先行編譯標頭

第一個編譯(建立先行編譯頭檔案的編譯程式)比後續的編譯時間長一點。 後續編譯可以藉由包含先行編譯的程序代碼,更快速地進行。

您可以先行編譯 C 和 C++ 程式。 在C++程序設計中,將類別介面資訊分成頭文件是常見的作法。 這些頭檔稍後可以包含在使用 類別的程式中。 藉由先行編譯這些標頭,您可以減少程式編譯所需的時間。

注意

雖然每個原始程序檔只能使用一個先行編譯頭檔 (.pch) 檔案,但您可以在專案中使用多個 .pch 檔案。

先行編譯程式代碼的兩個選項

您可以先行編譯任何 C 或 C++ 程式代碼;您不限於只先行編譯頭檔。

先行編譯需要規劃,但如果您先行編譯原始程序代碼,而不是簡單的頭檔,它會提供更快速的編譯。

當您知道原始程序檔使用一組常見的頭檔,或當您想要在先行編譯中包含原始程式碼時,先行編譯程序代碼。

先行編譯標頭選項為 /Yc [建立先行編譯頭檔]/Yu [使用先行編譯頭檔]。 用來 /Yc 建立先行編譯標頭。 搭配選擇性 hdrstop pragma 使用時, /Yc 可讓您先行編譯頭檔與原始程式碼。 選取 /Yu 以在現有的編譯中使用現有的先行編譯標頭。 您也可以搭配 /Yc/Yu 選項使用 /Fp ,以提供先行編譯標頭的替代名稱。

編譯程式選項參考文章, /Yu/Yc 討論如何在開發環境中存取這項功能。

先行編譯標頭一致性規則

因為 PCH 檔案包含電腦環境和程式記憶體位址資訊的相關信息,所以您應該只在建立程式的電腦上使用 PCH 檔案。

每個檔案使用先行編譯標頭的一致性規則

編譯 /Yu 程式選項可讓您指定要使用的 PCH 檔案。

當您使用 PCH 檔案時,除非另有指定,否則編譯程式會假設您在建立 PCH 檔案時生效的相同編譯環境。 編譯環境包含編譯程式選項、pragmas 等等。 如果編譯程式偵測到不一致,它會發出警告,並在可能的情況下識別不一致。 這類警告不一定表示 PCH 檔案發生問題;他們只是警告你可能的衝突。 下列各節將說明 PCH 檔案的一致性需求。

編譯程式選項一致性

使用 PCH 檔案時,下列編譯程式選項可能會觸發不一致的警告:

  • 使用預處理器 (/D) 選項建立的巨集,在建立 PCH 檔案和目前編譯的編譯之間必須相同。 未檢查已定義的常數狀態,但如果這些巨集變更,可能會發生無法預測的結果。

  • PCH 檔案不適用於 /E/EP 選項。

  • 在後續使用 PCH 檔案的編譯之前,必須先使用 [產生瀏覽資訊]/FR 選項或 [排除局部變數]/Fr選項來建立 PCH 檔案。

C 7.0 相容 (/Z7

如果這個選項在建立 PCH 檔案時生效,則使用 PCH 檔案的後續編譯可以使用偵錯資訊。

如果建立 PCH 檔案時,C 7.0 相容 (/Z7) 選項沒有生效,則稍後會使用 PCH 檔案進行編譯並 /Z7 觸發警告。 偵錯資訊會放在目前的 .obj 檔案中,而且 PCH 檔案中定義的本機符號無法供調試程式使用。

包含路徑一致性

PCH 檔案不包含標頭包含路徑的相關信息,該路徑在建立時生效。 當您使用 PCH 檔案時,編譯程式一律會使用目前編譯中指定的標頭 include 路徑。

來源檔案一致性

當您指定 [使用先行編譯頭檔 (/Yu)] 選項時,編譯程式會忽略將先行編譯的原始程式碼中出現的所有預處理器指示詞(包括 pragmas)。 這類預處理器指示詞所指定的編譯必須與用於建立先行編譯頭檔 (/Yc) 選項的編譯相同。

Pragma 一致性

在建立 PCH 檔案期間處理的 Pragmas 通常會影響稍後使用 PCH 檔案的檔案。 commentmessage pragmas 不會影響編譯的其餘部分。

這些 pragmas 只會影響 PCH 檔案內的程序代碼;它們不會影響稍後使用 PCH 檔案的程式代碼:

comment
linesize

message
page

pagesize
skip

subtitle
title

這些 pragmas 會保留為先行編譯標頭的一部分,並影響使用先行編譯標頭的編譯其餘部分:

alloc_text
auto_inline
check_stack
code_seg
data_seg

function
include_alias
init_seg
inline_depth

inline_recursion
intrinsic
optimize
pack

pointers_to_members
setlocale
vtordisp
warning

/Yc 和 /Yu 的一致性規則

當您使用使用 /Yc/Yu所建立的先行編譯標頭時,編譯程式會將目前的編譯環境與建立 PCH 檔案時存在的編譯環境進行比較。 請務必針對目前的編譯指定與上一個環境一致的環境(使用一致的編譯程式選項、pragmas 等等)。 如果編譯程式偵測到不一致,它會發出警告,並在可能的情況下識別不一致。 這類警告不一定表示 PCH 檔案發生問題;他們只是警告你可能的衝突。 下列各節說明先行編譯標頭的一致性需求。

編譯程式選項一致性

下表列出編譯程式選項,這些選項可能會在使用先行編譯標頭時觸發不一致的警告:

選項 名稱 規則
/D 定義常數和巨集 在建立先行編譯標頭和目前編譯的編譯之間必須相同。 未檢查已定義的常數狀態。 不過,如果您的檔案相依於變更常數的值,可能會產生無法預測的結果。
/E/EP 將預處理器輸出複製到標準輸出 先行編譯標頭不適用於 /E/EP 選項。
/Fr/FR 產生Microsoft來源瀏覽器資訊 若要讓 /Fr/FR 選項在 選項中有效 /Yu ,它們也必須在建立先行編譯標頭時生效。 後續使用先行編譯標頭的編譯也會產生來源瀏覽器資訊。 瀏覽器資訊會放在單 .sbr 一檔案中,並以與 CodeView 資訊相同的方式由其他檔案參考。 您無法覆寫來源瀏覽器資訊的位置。
/GA/GD/GE/Gw/GW Windows 通訊協議選項 在建立先行編譯標頭和目前編譯的編譯之間必須相同。 如果這些選項不同,編譯程式就會發出警告。
/Zi 產生完整的偵錯資訊 如果這個選項在建立先行編譯標頭時生效,則使用先行編譯的後續編譯可以使用該偵錯資訊。 如果 /Zi 建立先行編譯標頭時未生效,則會使用先行編譯的後續編譯,而 /Zi 選項會觸發警告。 偵錯資訊會放在目前的物件檔中,而且在先行編譯標頭中定義的本機符號無法供調試程式使用。

注意

先行編譯的標頭設施僅適用於 C 和 C++原始程序檔。

在專案中使用先行編譯標頭

上一節提供先行編譯標頭的概觀:/Yc 和 /Yu、/Fp 選項和 hdrstop pragma。 本節描述在專案中使用手動先行編譯標頭選項的方法;其結尾為makefile範例及其所管理的程序代碼。

如需在專案中使用手動先行編譯標頭選項的另一種方法,請研究Visual MFC\SRC Studio預設設定期間所建立之目錄中的其中一個makefiles。 這些 makefiles 會採用與本節中所呈現的檔案類似的方法。 它們會使用Microsoft程式維護公用程式 (NMAKE) 巨集,並提供更多建置程式的控制權。

建置程式中的 PCH 檔案

軟體專案的程式代碼基底通常包含在多個 C 或C++原始程式檔、物件檔、連結庫和標頭檔中。 一般而言,makefile 會將這些專案的組合協調成可執行檔。 下圖顯示使用先行編譯頭檔之makefile的結構。 此圖表中的 NMAKE 巨集名稱和檔名與 PCH 範例 makefile 中找到的範例程式代碼一致,以及 PCH 的範例程式代碼。

此圖使用三個圖表裝置來顯示建置程式的流程。 具名矩形代表每個檔案或巨集;三個巨集代表一或多個檔案。 陰影區域代表每個編譯或鏈接動作。 箭號會顯示編譯或鏈接程式期間合併的檔案和巨集。

 圖表會在圖表後面的文字中描述。
使用先行編譯頭檔之makefile的結構:

此圖顯示使用先行編譯頭檔之makefile的範例輸入和輸出。

此圖顯示 '$(STABLEHDRS)' 和 '$(BOUNDRY)' 饋送至 CL /c /W3 /Yc$(BOUNDRY) applib.cpp myapp.cpp。 的輸出為 $(STABLE。PCH)。 然後,applib.cpp和 $(UNSTABLEHDRS) 和 $(STABLE.PCH) 饋送至 CL /c /w3 /Yu $(BOUNDRY) applib.cpp,其會產生applib.obj。myapp.cpp、$(UNSTABLEHDR)和$(穩定)。PCH) 饋送至 CL /c /w3 /Yu $(BOUNDRY) myapp.cpp,其會產生myapp.obj。最後,連結 /NOD ONERROR:NOEXE $(OBJS)、myapp、NUL、$(LIBS)、NUL 結合applib.obj和myapp.obj,以產生myapp.exe。

從圖表頂端開始,和 BOUNDRY 都是 NMAKE 巨集,STABLEHDRS您可以在其中列出不太可能需要重新編譯的檔案。 這些檔案是由命令字串編譯

CL /c /W3 /Yc$(BOUNDRY) applib.cpp myapp.cpp

只有當先行編譯頭檔 (STABLE.pch) 不存在,或者您對兩個巨集中所列的檔案進行變更時,才會存在。 不論是哪一種情況,先行編譯頭檔只會包含巨集中所列檔案的程序 STABLEHDRS 代碼。 列出您想要在巨集中 BOUNDRY 先行編譯的最後一個檔案。

您在這些巨集中所列的檔案可以是標頭檔或 C 或 C++ 原始程式檔。 (單一 PCH 檔案無法與 C 和 C++ 來源搭配使用。您可以使用 hdrstop 巨集,在檔案內的 BOUNDRY 某個時間點停止先行編譯。 如需詳細資訊,請參閱hdrstop

在下一個圖表中, APPLIB.obj 代表最終應用程式中所使用的支援程序代碼。 其建立自 APPLIB.cpp、巨集中列出的 UNSTABLEHDRS 檔案,以及先行編譯頭檔中的先行編譯程序代碼。

MYAPP.obj 代表您的最終應用程式。 其建立自 MYAPP.cpp、巨集中列出的 UNSTABLEHDRS 檔案,以及先行編譯頭檔中的先行編譯程序代碼。

最後,可執行檔 (MYAPP.EXE) 是藉由連結巨集 (APPLIB.objMYAPP.obj) 中列出的OBJS檔案來建立。

PCH 的範例 makefile

下列makefile使用巨集和!IF、、 !ELSE!ENDIF 流程控制命令結構,以簡化其對項目的適應。

NMAKE
# Makefile : Illustrates the effective use of precompiled
#            headers in a project
# Usage:     NMAKE option
# option:    DEBUG=[0|1]
#            (DEBUG not defined is equivalent to DEBUG=0)
#
OBJS = myapp.obj applib.obj
# List all stable header files in the STABLEHDRS macro.
STABLEHDRS = stable.h another.h
# List the final header file to be precompiled here:
BOUNDRY = stable.h
# List header files under development here:
UNSTABLEHDRS = unstable.h
# List all compiler options common to both debug and final
# versions of your code here:
CLFLAGS = /c /W3
# List all linker options common to both debug and final
# versions of your code here:
LINKFLAGS = /nologo
!IF "$(DEBUG)" == "1"
CLFLAGS   = /D_DEBUG $(CLFLAGS) /Od /Zi
LINKFLAGS = $(LINKFLAGS) /COD
LIBS      = slibce
!ELSE
CLFLAGS   = $(CLFLAGS) /Oselg /Gs
LINKFLAGS = $(LINKFLAGS)
LIBS      = slibce
!ENDIF
myapp.exe: $(OBJS)
    link $(LINKFLAGS) @<<
$(OBJS), myapp, NUL, $(LIBS), NUL;
<<
# Compile myapp
myapp.obj  : myapp.cpp $(UNSTABLEHDRS)  stable.pch
    $(CPP) $(CLFLAGS) /Yu$(BOUNDRY)    myapp.cpp
# Compile applib
applib.obj : applib.cpp $(UNSTABLEHDRS) stable.pch
    $(CPP) $(CLFLAGS) /Yu$(BOUNDRY)    applib.cpp
# Compile headers
stable.pch : $(STABLEHDRS)
    $(CPP) $(CLFLAGS) /Yc$(BOUNDRY)    applib.cpp myapp.cpp

除了STABLEHDRS建置程式中 PCH 檔案中「使用先行編譯頭檔之 Makefile 的結構」中所示的 、 BOUNDRYUNSTABLEHDRS 巨集之外,此 makefile 還提供CLFLAGS巨集和LINKFLAGS巨集。 您必須使用這些巨集來列出不論您建置偵錯還是應用程式可執行檔最終版本的編譯程式和連結器選項。 此外還有一個 LIBS 巨集,您可以在其中列出專案所需的連結庫。

makefile 也會使用!IF!ELSE!ENDIF 來偵測您是否在 NMAKE 命令行上定義DEBUG符號:

NMAKE
NMAKE DEBUG=[1|0]

這項功能可讓您在開發期間使用相同的makefile,以及程式的最終版本。 用於 DEBUG=0 最終版本。 下列命令列有相同的效果:

NMAKE
NMAKE
NMAKE DEBUG=0

如需makefiles的詳細資訊,請參閱 NMAKE 參考。 另請參閱 MSVC 編譯程式選項MSVC 連結器選項

PCH 的範例程式代碼

下列來源檔案會用於建置程式中的 PCH 檔案中所述的 makefile 和 PCH 的範例 makefile。 批註包含重要資訊。

來源檔案 ANOTHER.H

C++
// ANOTHER.H : Contains the interface to code that is not
//             likely to change.
//
#ifndef __ANOTHER_H
#define __ANOTHER_H
#include <iostream>
void savemoretime( void );
#endif // __ANOTHER_H

來源檔案 STABLE.H

C++
// STABLE.H : Contains the interface to code that is not likely
//            to change. List code that is likely to change
//            in the makefile's STABLEHDRS macro.
//
#ifndef __STABLE_H
#define __STABLE_H
#include <iostream>
void savetime( void );
#endif // __STABLE_H

來源檔案 UNSTABLE.H

C++
// UNSTABLE.H : Contains the interface to code that is
//              likely to change. As the code in a header
//              file becomes stable, remove the header file
//              from the makefile's UNSTABLEHDR macro and list
//              it in the STABLEHDRS macro.
//
#ifndef __UNSTABLE_H
#define __UNSTABLE_H
#include <iostream>
void notstable( void );
#endif // __UNSTABLE_H

來源檔案 APPLIB.CPP

C++
// APPLIB.CPP : This file contains the code that implements
//              the interface code declared in the header
//              files STABLE.H, ANOTHER.H, and UNSTABLE.H.
//
#include "another.h"
#include "stable.h"
#include "unstable.h"
using namespace std;
// The following code represents code that is deemed stable and
// not likely to change. The associated interface code is
// precompiled. In this example, the header files STABLE.H and
// ANOTHER.H are precompiled.
void savetime( void )
    { cout << "Why recompile stable code?\n"; }
void savemoretime( void )
    { cout << "Why, indeed?\n\n"; }
// The following code represents code that is still under
// development. The associated header file is not precompiled.
void notstable( void )
    { cout << "Unstable code requires"
            << " frequent recompilation.\n";
    }

來源檔案 MYAPP.CPP

C++
// MYAPP.CPP : Sample application
//             All precompiled code other than the file listed
//             in the makefile's BOUNDRY macro (stable.h in
//             this example) must be included before the file
//             listed in the BOUNDRY macro. Unstable code must
//             be included after the precompiled code.
//
#include "another.h"
#include "stable.h"
#include "unstable.h"
int main( void )
{
    savetime();
    savemoretime();
    notstable();
}

另請參閱

比較標頭單位、模組和先行編譯標頭檔
C/C++ 建置參考
MSVC 編譯程式選項C++ 中的模組概觀
教學課程:使用模組匯入 C++ 標準程式庫
逐步解說:在您的 Visual C++ 專案中建置和匯入標頭單元
逐步解說:將 STL 連結庫匯入為標頭單位