使用 GNU CC 的預編譯頭文件加快編譯速度
既使用過 Microsoft® Visual C++® 又使用過 GNU CC 的網友一定會感受到兩者編譯速度的差異,尤其是對於 wxWidgets 這樣頭文件內容多的軟件。Microsoft® Visual C++® 能夠有很高編譯效率的原因是其支持“預編譯頭文件” (Pre-Compiled Header, PCH)。當使用 Microsoft® Visual C++® 建立項目時,常常會建立文件 StdAfx.cpp 和 StdAfx.h。其中 StdAfx.h 包含了項目中所有實質 C/C++ 源文件所要用到的一些系統頭文件,而 StdAfx.cpp 只包含了 StdAfx.h。這兩個文件便是用來建立預編譯頭文件“項目名.pch”的。預編譯頭文件是將一些項目中普遍使用的頭文件內容的詞法分析、語法分析等結 果緩存在一個特定格式的二進制文件中;當然編譯實質 C/C++ 源文件時,就不必從頭對這些頭文件進行詞法-語法分析,而只需要利用那些已經完成詞法-語法分析的結果就可以了。
事實上,GNU CC 從 3.4.x 版和 4.x 版開始,也支持了這種提高編譯效率的機制。只是由於 GNU CC 的手冊中的《Using Precompiled Headers》一節對此介紹不多,也沒有簡單的自動項目管理工具支持這項功能,因而許多網友還不知道 GNU CC 的這項功能。
GNU CC 的手冊中建議使用 make 管理預編譯頭文件,還指出 C 語言的預編譯頭文件和 C++ 語言的預編譯頭文件是不一樣的。這裏首先講述項目中只有 C 語言源文件或只有 C++ 語言源文件的情形,再講述項目中兩種語言的源文件同時存在的情況。
項目中只有 C 或 C++ 一種語言的源文件時,只需建立一個預編譯頭文件。
- 建立一個頭文件,例如命名爲 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_ */ - 建立 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 "$@" as target
$(EXE): $(OBJS) $(LIBBDD)
$(LD) $(LDFLAGS) -o $@ $> $^ $(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++ 語言源文件,就需要爲兩種語言分別維護一個預編譯頭文件。
- 再建立一個頭文件,例如命名爲 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_ */ - 在 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 "$@" as target
$(EXE): $(OBJS) $(LIBBDD)
$(LD) $(LDFLAGS) -o $@ $> $^ $(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