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

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

自訂先行編譯的程式碼

對於需要大量時間來建置的大型專案,您可能想要考慮建立自訂先行編譯的檔案。 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 檔案的檔案。 comment message pragmas 不會影響編譯的其餘部分。

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

    comment
    linesize

    message

    pagesize

    subtitle
    title

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

    alloc_text
    auto_inline
    check_stack
    code_seg
    data_seg

    function
    include_alias
    init_seg
    inline_depth

    inline_recursion
    intrinsic
    optimize

    pointers_to_members
    setlocale
    vtordisp
    warning

    /Yc 和 /Yu 的一致性規則

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

    編譯器選項一致性

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

    /Fr /FR 產生 Microsoft Source Browser 資訊 若要讓 /Fr /FR 選項在 選項中有效 /Yu ,它們也必須在建立先行編譯標頭時生效。 後續使用先行編譯標頭的編譯也會產生來源瀏覽器資訊。 瀏覽器資訊會放在單 .sbr 一檔案中,並以與 CodeView 資訊相同的方式由其他檔案參考。 您無法覆寫來源瀏覽器資訊的位置。 /GA /GD /GE /Gw /GW Windows 通訊協定選項 在建立先行編譯標頭和目前編譯的編譯之間必須相同。 如果這些選項不同,編譯器就會發出警告。 產生完整的偵錯資訊 如果這個選項在建立先行編譯標頭時生效,則使用先行編譯的後續編譯可以使用該偵錯資訊。 如果 /Zi 建立先行編譯標頭時未生效,則會使用先行編譯的後續編譯,而 /Zi 選項會觸發警告。 偵錯資訊會放在目前的物件檔中,而且在先行編譯標頭中定義的本機符號無法供偵錯工具使用。

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

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

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

    建置程式中的 PCH 檔案

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

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

    使用先行編譯標頭檔之 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)和 $(STABLE.PCH) 饋送至 CL /c /w3 /Yu $(BOUNDRY) myapp.cpp,其會產生 myapp.obj。最後,applib.obj 和 myapp.obj 會結合 LINK /NOD ONERROR:NOEXE $(OBJS)、myapp、NUL、$(LIBS)、NUL 來產生 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.obj MYAPP.obj ) 中列出的 OBJS 檔案來建立。

    PCH 的範例 makefile

    下列 makefile 使用宏和 !IF 、、 !ELSE !ENDIF 流程式控制制命令結構,以簡化其對專案的適應。

    # 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 DEBUG=[1|0]
    

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

    NMAKE
    NMAKE DEBUG=0
    

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

    PCH 的範例程式碼

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

    原始程式檔 ANOTHER.H

    // 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

    // 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

    // 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

    // 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

    // 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 程式庫匯入為標頭單位

  •