1. 程式人生 > >gch檔案之淺談GCC預編譯頭技術 收藏

gch檔案之淺談GCC預編譯頭技術 收藏

其 實剛開始程式設計的時候,我是絲毫不重視編譯速度之類的問題的,原因很簡單,因為那時我用BASICA。後來一直用到C++ Builder,儘管Borland的廣告無時無刻不在吹噓其編譯速度,我卻從沒有對這個問題上心過,因為心裡根本沒有“編譯速度慢”這種概念。沒有壞, 哪來好?所謂矛盾的對立統一。遇到的第一個“慢”的編譯器也許是javac,但因為Java的特殊性,也就容忍了。真正接觸到世間的“惡勢力”,還要算是 第一次使用GCC的時候……準確地說是MinGW。開源世界曾給我諸多驚喜,其一就是原來編譯器也可以這麼慢的。那時我不禁對開源社群肅然起敬,他們就用 這樣的編譯器,建立起了怎樣一個多彩的世界!也在那時才明白了,Borland其實真的很了不起。

時至今日我也不是很瞭解Borland是怎麼做到的,很久以來也不知道GCC是差在了哪裡。然而……有一次心血來潮,忽然想看看 MinGW編譯過程中載入的所有標頭檔案。於是用了一下 -H 引數。結果是滿意的,載入的標頭檔案真多呀。接下來……開始感覺到另外的一些東西了。敢情,大部分編譯時間是浪費在這裡的呀?——“預編譯頭”的概念如鯨魚 般躍出腦海。

預編譯頭技術是在VC中第一次瞭解的,其對編譯速度的提高,絕對給人以深刻的印象。使用MinGW的時候居然忘了這個古老的咒語。是否正是我所需要的?百 度幾下,結果令人失望,這方面的文獻少得可憐,更令人沮喪的是還有不少人相信GCC是沒有預編譯頭技術的。賊心不死的我開啟 GCC官方文件,查詢precompiled headers。慢著,居然如此順利!——官方文件討論篇幅並不長,但足以讓我喊萬歲了~不用多,一句話就夠了,怎麼說來著?Simply compile it!

所謂預編譯頭,就是把標頭檔案事先編譯成一種二進位制的中間格式,供後續的編譯 過程使用。不要把這個中間格式與. o/.obj/.a/.lib的格式混淆,他們是截然不同的,所以預編譯標頭檔案的特性和目標檔案也不同(儘管他們都屬於某種中間檔案)

。——但也有類似的地方的,比如,它們都是編譯器之間不相容的^_^,就是說你不能把VC生成的預編譯頭拿到GCC上去用。甚至副檔名都不一樣,VC的是 大家都熟悉的. pch,而GCC的,是.gch——今天的主角。

為什麼要使用預編譯頭?再明確不過了,提高編譯速度。為什麼會提高編譯速度?這麼說吧,你有兩個檔案a.cpp和b.cpp,都包含了同一個標頭檔案 c.h。那麼正常的流程是:將c.h和a.cpp合併,編譯成a.o;將c.h和b.cpp合併,編譯成b.o;最後將a.o和b.o連結成可執行檔案。 過程很簡單,浪費時間之處也一目瞭然:標頭檔案c.h的內容實際上被解析了兩遍。也許你要說,當然要兩遍了,因為標頭檔案幾乎是不生成任何程式碼的,只有依附於 具體的.cpp檔案才有意義。正確,但那只是在程式碼執行過程中。但在程式碼編譯的時候呢?編譯器讀入原始碼,首先將其解析成為一種內部的表示方式。這個過程 與其所依附的.cpp檔案並無關係,編譯器接著可以讀入.cpp檔案並同樣解析成內部表示,然後把兩段內部表示拼接起來,再進行接下來的操作。既然編譯兩 個.cpp檔案都要先對c.h進行解析,那幹嘛不把c.h解析好了儲存成臨時檔案,用時讀入,不就可以省了一次解析的時間了嗎?——預編譯頭技術節省時間 的原理正在於此,尤其是在這樣一個事實下:對原始碼的“解析”這個步驟,確實是佔了編譯時間中很可觀的一部分。

我看見你滿是狐疑的臉:預處理,就是編譯之前的處理,合併.h和.cpp檔案分明是預處理的步驟,而解析原始碼是編譯之中的步驟,先解析後合併?怎麼 “預”處理反而跑到編譯步驟之後了?這還叫“預”嗎?——這個問題我們決定不深究了,畢竟現在的編譯器早就混淆了預處理與編譯的界限……畢竟,這麼做是管 用的,對嗎?

我們來看看結果。寫一個C++的Hello world,使用cout輸出一行字。包含了什麼標頭檔案?當然是iostream。這個標頭檔案對於人們來說,絕對是熟視無睹級別的。然而使用它的時候,你 注意到編譯器幕後的累累“罪行”了嗎?是的,用 -H 引數編譯一下這個Hello world吧!看看總共載入了多少個頭檔案?我的機器上,總共103個!

是的,你應該將它們做成一個.gch檔案。如何做?如前所述,再簡單不過:只要編譯它就可以了:

g++ xxx.h

一句話,就是:把.h檔案當成.cpp檔案一樣來編譯。這是最簡單的,如果需要控制編譯細節,比如常量定義之類,大可加上其它選項。執行之後,你會發現同 個目錄裡生成了一個名叫xxx.h.gch的檔案,這就是我們要的。也許你和我一樣,迫不及待地嘗試g++ iostream了?呵呵,結果一定是和我一樣的失敗——在編譯.gch的過程中,GCC並沒有使用環境變數或 -I 選項來查詢被編譯的標頭檔案,被編譯的標頭檔案必須在當前目錄下。然而,被編譯的標頭檔案所進一步包含的其它標頭檔案,卻可以通過以上途徑找到。簡言之,就是把直 接編譯的那個標頭檔案以類似對待.cpp檔案的方式處理了。現在知道該如何編譯iostream了吧?對,在當前目錄裡建立一個頭檔案,起個隨你喜歡的名 字,比如foo.h,在其裡面寫上:#include <iostream>,然後編譯它:g++ foo.h。生成的foo.h.gch,就是我們要的了。其它檔案需要用到iostream的,不要包含iostream,要包含foo.h。切記,不是 去包含foo.h.gch!

如果你用過VC,那麼這個foo.h也許會讓你找到一種似曾相識的感覺吧?對了,就相當於那個 stdafx.h
!那麼你也該記得,每個檔案包含這個foo.h,都應該在檔案一開始的地方,否則會出錯。真的,終於找到了GCC中的stdafx.h,這種感覺幾乎讓人 熱淚盈眶了^_^

那麼接下來,照搬一些stdafx.h相關的注意事項吧,它們同樣適用於.gch檔案:應該把那些不常修改的(首當其衝,當然是系統的)標頭檔案放在預編譯 頭裡,而那些屬於你的程式的一部分的標頭檔案,一般並不放在預編譯頭裡,因為它們可能隨時要被修改的。每修改一次就要重新生成預編譯頭,並沒有速度優勢可 言,失去預編譯頭的意義了。另外重要的注意事項是:如果你生成預編譯頭的時候用了一些選項,比如巨集定義,那麼使用這個預編譯頭的其它原始碼檔案,被編譯的 時候也要使用這些選項,否則會因為不匹配而編譯失敗。

對了,說了半天,從來沒有正面講過如何使用已經生成的預編譯頭。然而看到這裡也該明白了,是的,很簡單,只要包含其所對應的.h檔案即可!比如你有個頭文 件叫foo.h,另外有一大堆其它檔案都包含了這個foo.h,原來沒有使用預編譯頭技術,現在忽然想使用了,於是把foo.h編譯成了 foo.h.gch。那其它檔案要做怎樣的修改?——什麼都不用,一切照舊!聰明的GCC編譯器在查詢一個.h檔案之前,會自動查詢其目錄裡有沒有對應 的.gch檔案,如有,且可用,則用之;沒有,才用到真正的.h標頭檔案。——慢著,“如有,且可用”,什麼叫“可用”?——就是指這個.gch格式要正 確,版本要相容,而且如上所述,編譯兩者要用同樣的選項。如果.gch不可用,編譯器會給出一條警告,告訴我們:這個預編譯頭不能用!我只好用原有的.h 標頭檔案啦!什麼?你說看不到這個警告?——當然,要先開啟 -Winvalid-pch 選項才行,其預設是關閉的。

用 -H 選項感受一下預編譯頭的清爽吧!再沒有滾不完的標頭檔案了,明顯提高的速度,絕對會讓你有種翻身解放的感覺,原來MinGW也可以和蝸牛般的速度說再見的。

筆者後記:有一次同事不小心生成了.gch,但是由於編譯選項不一致,導致後面無法編譯。小心地雷啊

///////////////////////////////////////

GNU CC 從 3.4.x 版和 4.x 版開始,也支援了這種提高編譯效率的機制。只是由於 GNU CC 的手冊中的《Using Precompiled Headers》一節對此介紹不多,也沒有簡單的自動專案管理工具支援這項功能,因而許多網友還不知道 GNU CC 的這項功能。

GNU CC 的手冊中建議使用 make 管理預編譯標頭檔案,還指出 C 語言的預編譯標頭檔案和 C++ 語言的預編譯標頭檔案是不一樣的。這裡首先講述專案中只有 C 語言原始檔或只有 C++ 語言原始檔的情形,再講述專案中兩種語言的原始檔同時存在的情況。

專案中只有 C 或 C++ 一種語言的原始檔時,只需建立一個預編譯標頭檔案。

  1. 建立一個頭檔案,例如命名為 inc.h。該檔案供專案中所有的 C/C++ 原始檔使用。將整個專案所需要的標頭檔案都列在其中:
    /* $FreeBSD$ */
    #ifndef	_INC_H_
    #define	_INC_H_
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <sys/uio.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #endif /* ! _INC_H_ *
  2. 建立 Makefile,以維護預編譯標頭檔案。一方面要建立維護 GNU CC 的預編譯標頭檔案 inc.h.gch 的規則;另一方面,要在編譯每個 C/C++ 原始檔時檢查 inc.h.gch,即讓所有 .o 檔案依賴於 inc.h.gch。
    # $FreeBSD$
    
    CC	=	gcc
    CFLAGS	=	-g -Wall
    
    CXX	=	gcc
    CXXFLAGS	=	-g -Wall
    
    LD	=	gcc
    LDFLAGS	=	-g -Wall
    
    EXE	=	testapp
    PCH_H	=	inc.h
    PCH	=	inc.h.gch
    SRCS	=	testapp.c
    OBJS	=	testapp.o
    LIBS	=			# System Libraries
    
    ECHO	=	echo
    CP	=	cp -v
    RM	=	rm -f
    
    .SUFFIXES:
    .SUFFIXES: .o .c .cxx
    
    # The meaning of "$<":
    #     BSD Pmake: the implied source
    #     GNU make: the first prerequisite
    
    .c.o:
    	$(CC) $(CFLAGS) -c $<
    .cxx.o:
    	$(CXX) $(CXXFLAGS) -c $<
    
    all:	$(EXE)
    
    #                  $>                    $^
    # BSD Pmake    all sources        not defined
    # GNU make     not defined        all prerequisites
    # Both interpret "[email protected]" as target
    
    $(EXE):	$(OBJS) $(LIBBDD)
    	$(LD) $(LDFLAGS) -o [email protected] $> $^ $(LIBS)
    
    # Pre-compiled header
    $(OBJS): $(PCH)
    
    $(PCH): $(PCH_H)
    	$(CC) $(CFLAGS) $> $^
    
    clean:
    	$(RM) $(PCH) $(OBJS)
    # For Both UNIX-like OS and Microsoft Windows (MinGW/Cygwin)
    	$(RM) $(EXE) $(EXE).exe

如果專案既包含 C 語言原始檔,也包含 C++ 語言原始檔,就需要為兩種語言分別維護一個預編譯標頭檔案。

  1. 再建立一個頭檔案,例如命名為 inc.hpp。inc.h 供 C 語言原始檔使用,而 inc.hpp 供 C++ 語言檔案使用。假如 inc.hpp 的內容與 inc.h 的相同,只需要簡單的寫上:
    /* $FreeBSD$ */
    #ifndef	_INC_HPP_
    #define	_INC_HPP_
    
    #include "inc.h"
    
    #endif /* ! _INC_HPP_ */
  2. 在 Makefile 裡也要隨之增加對 inc.hpp 的維護。一是要增加產生 inc.hpp.gch 的規則,此時執行 GNU CC 時要增加引數“-x c++-header”;二是要在 clean 一節中刪除這個預編譯標頭檔案。
    # $FreeBSD$
    
    CC	=	gcc
    CFLAGS	=	-g -Wall
    
    CXX	=	gcc
    CXXFLAGS	=	-g -Wall
    
    LD	=	gcc
    LDFLAGS	=	-g -Wall
    
    EXE	=	testapp
    PCH_H	=	inc.h
    PCH	=	inc.h.gch
    PCH_X_H	=	inc.hpp
    PCH_X	=	inc.hpp.gch
    SRCS	=	testapp.c
    OBJS	=	testapp.o
    LIBS	=			# System Libraries
    
    ECHO	=	echo
    CP	=	cp -v
    RM	=	rm -f
    
    .SUFFIXES:
    .SUFFIXES: .o .c .cxx
    
    # The meaning of "$<":
    #     BSD Pmake: the implied source
    #     GNU make: the first prerequisite
    
    .c.o:
    	$(CC) $(CFLAGS) -c $<
    .cxx.o:
    	$(CXX) $(CXXFLAGS) -c $<
    
    all:	$(EXE)
    
    #                  $>                    $^
    # BSD Pmake    all sources        not defined
    # GNU make     not defined        all prerequisites
    # Both interpret "[email protected]" as target
    
    $(EXE):	$(OBJS) $(LIBBDD)
    	$(LD) $(LDFLAGS) -o [email protected] $> $^ $(LIBS)
    
    # Pre-compiled header
    $(OBJS): $(PCH)
    
    $(PCH): $(PCH_H)
    	$(CC) $(CFLAGS) $> $^
    
    $(PCH_X): $(PCH_X_H)
    	$(CXX) $(CXXFLAGS) -x c++-header $> $^
    
    clean:
    	$(RM) $(PCH) $(PCH_X) $(OBJS)
    # For Both UNIX-like OS and Microsoft Windows (MinGW/Cygwin)
    	$(RM) $(EXE) $(EXE).exe

這是以上兩種 Makefile 的比較:

@@ -12,6 +12,8 @@
 EXE	=	testapp
 PCH_H	=	inc.h
 PCH	=	inc.h.gch
+PCH_X_H	=	inc.hpp
+PCH_X	=	inc.hpp.gch
 SRCS	=	testapp.c
 OBJS	=	testapp.o
 LIBS	=			# System Libraries
@@ -48,7 +50,10 @@
 $(PCH): $(PCH_H)
 	$(CC) $(CFLAGS) $> $^
 
+$(PCH_X): $(PCH_X_H)
+	$(CXX) $(CXXFLAGS) -x c++-header $> $^
+
 clean:
-	$(RM) $(PCH) $(OBJS)
+	$(RM) $(PCH) $(PCH_X) $(OBJS)
 # For Both UNIX-like OS and Microsoft Windows (MinGW/Cygwin)
 	$(RM) $(EXE) $(EXE).exe

相關推薦

gch檔案GCC編譯技術 收藏

其 實剛開始程式設計的時候,我是絲毫不重視編譯速度之類的問題的,原因很簡單,因為那時我用BASICA。後來一直用到C++ Builder,儘管Borland的廣告無時無刻不在吹噓其編譯速度,我卻從沒有對這個問題上心過,因為心裡根本沒有“編譯速度慢”這種概念。沒有壞, 哪來好?

JavaScript編譯原理

全局變量 obj 預處理 http 對象 自然 net jet object 這兩天又把js的基礎重新復習了一下,很多不懂得還是得回歸基礎,大家都知道js是解釋性語言,就是編譯一行執行一行,但是在執行的之前,系統會做一些工作: 1,語法分析; 2,預編譯; 3,解釋執行。

資料結構—線性表單鏈表有結點和無頭節點

有頭結點的連結串列統一了演算法的實現,無頭節點減少了節點個數,但是隻有根據實際情況選用真正的有無頭節點連結串列 待續://程式碼實現 待續://程式碼實現 待續://程式碼實現 /*****************************************

無法開啟編譯檔案的解決方法及編譯原理[ZZ]

1。用VC.NET編輯程式,按Ctrl+F7,出現下列錯誤: fatal error C1083: 無法開啟預編譯標頭檔案:“Debug/UGFace.pch”: No such file or  directory   解決方法:修改:專案->屬性->C/C

error無法開啟編譯檔案的解決方法及編譯原理

1。用VC.NET編輯程式,按Ctrl+F7,出現下列錯誤: fatal error C1083: 無法開啟預編譯標頭檔案:“Debug/UGFace.pch”: No such file or directory 解決方法:修改:專案->屬性->C/C++

React | 高效前端

react 前端 編程 React | 高效前端之淺談 React在國外已被各個公司的各種產品大量使用,大眾熟知的INS、Airbnb、Yahoo、ThoughtWorks等,都是使用React來實現UI開發的。很多人認為,雖然React在國外已經被廣泛應用,但在國內,仍處新興萌芽階段。到底R

Linux系統編程進程間通信信號

編程 不能 status 系統編程 編寫 sim 發送信號 存在 就會 我們接著談Linux學習過程中一個重要的話題--信號。一、信號的概念: 信號是一種軟件中斷,它提供了一種處理異步事件的方法,也是進程間唯一的異步通信方式。二、信號的來源: 1、

FineBI學習系列FineBI和Tableau對比異同(從產品理念和功能對比)(圖文詳解)

研究 簡單 nio 比較 管理 post 企業it 獨立 圖片   不多說,直接上 幹貨!   FineBI和Tableau是比較好的自助式商業智能軟件,功能都很強大,是企業數據可視化不可或缺的利器,但兩款產品還是有非常大的區別的

FineBI學習系列FineBI產品理念

src 答疑 png java 商業 多說 企業級 python gpo     不多說,直接上幹貨!   FineBI是帆軟公司推出的自助式商業智能軟件,通過大數據引擎FineIndex,可以自動建模,傻瓜式操作,用戶只需在Dashboard中簡

C#虛方法和抽象方法的區別

C# 虛方法 抽象方法 多態 抽象類:有時我們表達一些抽象的東西,它是一種概括,不需要它成為一種實體,所以面向對象便有了抽象類。具體來講:一個員工,它屬於一個公司,但是公司只是一個名稱,我們不需要它成為一個實體,所以公司就是一個抽象類。何時必須聲明一個類為抽象類?(面試題)當這個類中包含抽象方

Linux下用戶和組

ack 總結 d參數 修改密碼 group 分享 password 使用 多個 關於Linux下用戶和組的總結。用戶:獲取系統資源權限的集合:組:具有相同權限的用戶的集合用戶組分類; 1.普通用戶組:可以加入多個用戶 2.系統組:一般加入一些系統用戶

C++---使用VS在C++程式設計中出現 fatal error C1010: 在查詢編譯時遇到意外的檔案結尾。是否忘記了向源中新增“#include "stdafx.h"”?

啦啦啦,好久沒寫部落格啦... 對於C++初學者來說適應一個新的編譯器還是需要蠻長一段時間的,現在我就給你們說說標題所說的這個問題吧... 第一步:選單--〉專案--〉設定,出現“專案設定”對話方塊,左邊展開專案,在“原始檔”中找到出錯的檔案。 第二步:在右邊選擇“C/C++”屬性頁,在Category

安防RSA

上一篇文章,我們瞭解了一下Hash演算法,那麼這篇文章,我們一起來了解一下RSA. RSA概述 首先看這個加密演算法的命名.很有意思,它其實是三個人的名字.早在1977年由麻省理工學院的三位數學家Rivest、Shamir 和 Adleman一起提出了這個加密演算法,並且用他們三個人

安防Hash

Hash,一般翻譯做“雜湊”,也有直接音譯為“雜湊”的,就是把任意長度的輸入通過雜湊演算法變換成固定長度的輸出,該輸出就是雜湊值。這種轉換是一種壓縮對映,也就是,雜湊值的空間通常遠小於輸入的空間,不同的輸入可能會雜湊成相同的輸出,所以不可能從雜湊值來確定唯一的輸入值。簡單的說就是一種將任意長

web前端開發技術對HTML5 智能表單的理解

提示 goods 表單 加載完成 空格 日期和時間 url 顯示 指向 Html5新增input的form屬性,用於指向特定form表單的id,實現input無需放在form標簽之中,即可通過表單進行提交。 <FORM id=xinzeng> … </FO

web前端技術基礎課程講解對soket的理解

淺談對soket的理解 定義: 網路上的兩個程式通過一個雙向的通訊鏈實現資料的交換,這個連結的一端就成為Socket 它是程序通訊的一種,即呼叫這個網路庫的api函式實現分佈在不同主機相關程序之間的資料交換,依照tcp/ip協議分給每個主機的網路地址,如果兩個主機要進行通訊,任何一個程序都要首先知道對方

線段樹的擴充套件zkw線段樹

線段樹的擴充套件之淺談zkw線段樹 轉自:https://khong-biet.blog.luogu.org/Introduction-of-zkwSegmentTree 2018-08-07 upd: 更新了線段樹測試(聽說資料加強了,所以把老記錄換掉) 更新了圖片

vs錯誤描述:fatal error C1010:在查詢編譯時遇到意外的檔案結尾。是否忘記了向源中新增“

錯誤描述:fatal error C1010:在查詢預編譯頭時遇到意外的檔案結尾。是否忘記了向源中新增“#include"stdafx.h"” 這個問題不一定是配置了使用預編譯頭造成的(專案-屬性-配

撩課-JavaWebStatement介面與編譯語句及呼叫儲存過程

Statement介面 介面 Statement介面作用 用於進行Java程式和資料庫之間的資料傳輸 具體類有3個實現 Statement 用於對資料庫進行通用訪問,使用的是靜態sql PreparedStatement PreparedSta

企業安全建設辦公網安全

前言 在大多數網際網路公司,安全建設的主要精力都投入在業務網安全上,辦公網往往成為短板。為避免教科書式的理論說教,本文以攻防的角度,以中型網際網路公司為例,討論下辦公網安全建設。這裡的辦公網是狹義的辦公網,僅包括員工辦公的網路區域,支撐辦公的erp、郵件等系統不包含在內。 辦公網滲透思路 辦公網通