不同編譯器是如何處理預編譯頭文件的?
最近為了給xmake實現預編譯頭文件的支持,研究了下各大主流編譯器處理預編譯頭的機制以及之間的一些差異。
現在的大部分c/c++編譯器都是支持預編譯頭的,例如:gcc,clang,msvc等,用於優化c++代碼的編譯速度,畢竟c++的頭文件如果包含了模板定義的話,編譯速度是很慢的, 如果能夠吧大部分通用的頭文件放置在一個header.h中,在其他源碼編譯之前預先對其進行編譯,之後的代碼都能重用這部分預編譯頭,就可以極大程度上減少頻繁的頭文件冗餘編譯。
但是不同編譯器對它的支持力度和處理方式,還是有很大差異的,並不是非常通用,在xmake中封裝成統一的介面和使用方式,還是費了很大的功夫才搞定。
msvc的預編譯頭處理
預編譯頭在msvc的項目中很常見,經常會看到類似stdafx.cpp, stdafx.h的文件,就是用於此目的,而msvc編譯器是通過編譯stdafx.cpp來生成預編譯頭文件stdafx.pch的。
創建預編譯頭的命令行如下:
$ cl.exe -c -Yc -Fpstdafx.pch -Fostdafx.obj stdafx.cppn
其中,-Yc就是創建預編譯頭stdafx.pch的意思,通過-Fp來指定*.pch的輸出文件路徑,用-Fo指定編譯stdafx.cpp生成對象文件。
其他源碼是如何使用這個stdafx.pch的呢,通過將stdafx.h傳入-Yu來告訴編譯器,編譯當前代碼,忽略#include "stdafx.h",直接使用已經編譯好的stdafx.pch文件。
$ cl.exe -c -Yustdafx.h -Fpstdafx.pch -Fotest.obj test.cppn
最後鏈接的時候,需要把:stdafx.obj, test.obj都連接上才行,這個也是和gcc, clang編譯器不同的地方。
$ link.exe -out:test test.obj stdafx.objn
註:一定要吧stdafx.obj也鏈接上哦,雖然stdafx.cpp僅用於生成stdafx.pch,但是對象文件也是需要。
還有個跟gcc, clang有區別的地方是,msvc的-Yu指定stdafx.h必須是#include "stdafx.h"中的頭文件名字,不是文件路徑哦。
clang的預編譯頭文件處理
個人感覺clang的預編譯頭文件支持方式最為友好,也最為簡單。
相比於msvc,不需要stdafx.cpp,只需要一個頭文件stdafx.h就可以生成pch文件。 相比於gcc,可以靈活控制pch文件的路徑,更加靈活。
編譯頭文件生成pch文件:
$ clang -c -o stdafx.pch stdafx.hn
使用預編譯頭文件:
$ clang -c -include stdafx.h -include-pch stdafx.pch -o test.o test.cppn
其中-include stdafx.h用於忽略編譯test.cpp中的#include "stdafx.h",通過-include-pch使用預先編譯好的stdafx.pch。
並且這裡指定的stdafx.h和stdafx.pch不僅可以是在includedir搜索路徑下的文件,也可以指定全路徑文件名,非常靈活,例如:
$ clang -c -include inc/stdafx.h -include-pch out/stdafx.pch -o test.o test.cppn
gcc的預編譯頭文件處理
gcc的預編譯頭處理方式基本上跟clang的類似,唯一的區別就是:它不支持-include-pch參數,因此不能所以指定使用的stdafx.pch文件路徑。
它有自己的搜索規則:
- 從stdafx.h所在目錄中,查找stdafx.h.pch文件是否存在
- 從-I的頭文件搜索路徑找查找stdafx.h.pch
編譯頭文件生成pch文件:
$ gcc -c -o stdafx.pch stdafx.hn
使用預編譯頭文件:
$ gcc -c -include stdafx.h -o test.o test.cppn
為了讓上述代碼正常編譯,stdafx.h.pch必須放置在stdafx.h的相同目錄下,這樣編譯才能找到,目前我還沒有找到可以所以指定輸出目錄的方法。
其他注意點
gcc、clang對於*.h的頭文件編譯,默認是作為c預編譯頭來使用的,這跟c++的pch是不一樣的,無法給c++的代碼使用,如果要生成c++可用的pch文件,必須要告訴編譯器,如何去編譯stdafx.h。
這個可以通過-x c++-header參數來解決:
$ gcc -c -x c++-header -o stdafx.pch stdafx.hn
當然也可以通過修改後綴名來解決:
$ gcc -c -o stdafx.pch stdafx.hppn
xmake對預編譯頭文件的支持
xmake支持通過預編譯頭文件去加速c/c++程序編譯,目前支持的編譯器有:gcc, clang和msvc。
使用方式如下:
target("test") n set_precompiled_header("header.h") n
通常情況下,設置c頭文件的預編譯,這需要加上這個配置即可,如果是對c++頭文件的預編譯,改成:
target("test") n set_precompiled_header("header.hpp") n
其中的參數指定的是需要預編譯的頭文件路徑,相對於當前xmake.lua所在的目錄。
如果只是調用xmake命令行進行直接編譯,那麼上面的設置足夠了,並且已經對各個編譯器進行支持,但是有些情況下,上面的設置還不能滿足需求:
- 如果要使用xmake project工程插件生成vs工程文件,那麼還缺少一個類似stdafx.cpp的文件(上面的設置在msvc編譯的時候會自動生成一個臨時的,但是對IDE工程不友好)。
- 如果gcc/clang下,header.h想作為c++的預編譯頭文件就不支持了,除非改成header.hpp(默認會當做c頭文件進行預編譯)。
因此為了更加地通用跨平台,可以在工程裡面創建一個類似vc中stdafx.cpp的源文件:header.cpp。
target("test") n set_precompiled_header("header.h", "header.cpp") n
header.cpp的內容如下:
#include "header.h"n
上面的設置,就可以很好地處理各種情況下的預編譯處理,追加的header.cpp也告訴了xmake:header.h是作為c++來預編譯的。
相對於經典的vc工程中的stdafx.cpp和stdafx.h,也能完美支持:
target("test") n set_precompiled_header("stdafx.h", "stdafx.cpp") n
更多使用說明見:target.set_precompiled_header
參考資料
Speed up C++ compilation, part 1: precompiled headers
原文出處:不同編譯器對預編譯頭文件的處理
推薦閱讀:
※[八卦] Phoenix Compiler Infrastructure或許那個有望開源?
※xmake 源碼架構剖析
※庖丁解牛迭代器,聊聊那些藏在幕後的秘密
※c++類的某個成員的析構函數是刪除的,會導致類合成的默認構造函數也是刪除的?
※編譯器簡介: 在 Siri 前時代如何與計算機對話