歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> makefile高級技巧

makefile高級技巧

日期:2017/2/27 16:00:55   编辑:Linux教程
Linux下構建一個C/C++工程一般都會使用Make工具,這就避免不了寫makefile。相對於Windows平台上visual studio的人性化構建,手動寫makefile確實是一件頭痛的事,特別是構建一個大型的工程。一些人可能覺得makefile不但難寫,構建功能還不如vs強大,當然前者我不否認,但是若能靈活運用的話,makefile的自動化構建能力絕對超過vs。這裡不打算講makefile的基本使用(初學者可以參考官方文檔和網上的makefile寫法示例),主要分享下makefile的一些高級技巧和常見構建問題的解決方案。

一、頭文件依賴問題
一個大型c++項目會包含大量源文件,如果每次構建都編譯所有cpp文件的話會讓你等的痛不欲生。因此我們可以只對修改過的cpp文件進行編譯,當然makefile也提供了這個基本功能,這是通過文件的修改時間來判定,比如”user.o: user.cpp”表示若user.cpp的修改時間比user.o更新的話才執行編譯命令。但這裡有個問題,若user.cpp中include了”foo.h”頭文件,這個頭文件有修改而user.cpp沒修改makefile也不會重新編譯user.cpp,這不是我們所希望的。一個簡單的解決方法是把user.cpp包含的所有頭文件名都寫成user.o的依賴,這樣頭文件的修改也會引起user.o的重新生成,但是如果foo.h又包含了其他頭文件呢,難道要把user.cpp直接和間接包含的所有文件名都手動寫到makefile裡嗎?如果包含的頭文件列表有改動也要人肉改寫makefile嗎?你不覺得這樣會吐血嗎?我們很自然的會想有沒有自動維護頭文件依賴的方法,遺憾的是makefile沒有提供這個功能,不過編譯器卻提供了。gcc\g++有個預處理選項”-MM”,可以把源文件的所有直接或間接包含的非系統路徑(系統路徑頭文件指用尖括號包含的頭文件,如<vector>,一般認為不會被修改) 的頭文件以makefile可讀的格式輸出出來,我們可以把這個依賴關系保存成一個依賴文件user.d,在makefile中include這個文件。另外,user.d這個依賴文件的重新生成也應依賴於user.cpp和其包含的頭文件的修改,也就是和user.o的依賴關系一致,所以我們要在user.o之後、冒號之前加上”user.d”,最終的user.d內容如下:

user.o user.d: user.cpp foo.h ….

官方文檔提供的makefile寫法如下:

%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed ‘s,\($*\)\.o[ :]*,\1.o $@ : ,g’ < $@.$$$$ > $@; \
rm -f $@.$$$$

include $(sources:.c=.d)

不解釋。

二、makefile的遞歸構建
一個簡單的軟件寫一個makefile就可以了,所有源文件的編譯和最終鏈接全搞定。大型的項目則由許多的庫組成,我們把許多一個模塊的許多源文件編譯成一個庫文件(靜態鏈接或動態鏈接均可),最終把這些庫文件鏈接成可執行文件。這和vs的解決方案與項目類似。我們可以在每個庫目錄下寫一個makefile,負責構建這個庫文件。另外還需一個最終鏈接的總makefile,他會調用所有配置好的庫目錄下的makefile,然後按需進行鏈接,鏈接命令依賴的是那些庫文件名,如果所有的庫文件都沒有更新的就不需重新鏈接了。
示例如下:

SUBDIRS = foo bar baz #所有庫目錄路徑
LIBS = foo.a bar.a baz.a #庫文件名

.PHONY: all
all: subdirs #先編譯所有庫文件
$(MAKE) $(PROG) #執行鏈接任務

$(PROG): $(LIBS) #若庫文件更新才進行鏈接
鏈接命令

.PHONY: subdirs $(SUBDIRS)

subdirs: $(SUBDIRS)

$(SUBDIRS):
$(MAKE) -C $@ #調用庫目錄下的makefile

三、debug和release版本分離
vs的項目構建可以選擇debug和release版本,兩者互不影響,在makefile中怎麼實現呢?通常做法是編譯不同版本的object文件,它們可以放在不同的目錄下,如vs就會分別放在debug和release目錄下。
想偷懶的話也可以放在一個目錄,但object文件的後綴不能相同,如release版本的用”.o”,debug版本的用”.do”,這樣也需要不同版本的依賴文件(因為依賴文件中寫明了object文件名),可以分別用”.d”和”.dd”作後綴。
示例如下:
ifeq ($(DEBUG),1) #debug版本
CXXFLAGS_EXTRA=-pg -g #開啟調試和profile編譯選項
DEP_TAIL=.dd
OBJ_TAIL=.do
PROG := /usr/local/nmt/bin/nmt_server_debug
else #release版本
CXXFLAGS_EXTRA=-O3 #開啟最高優化編譯選項
DEP_TAIL=.d
OBJ_TAIL=.o
PROG := /usr/local/nmt/bin/nmt_server
endif

之後把這些變量放在合適的地方,你懂的。

四、並行構建
默認情況下,make是單任務構建的,但現今的cpu都是多核的,如果可以同時編譯不同的文件可以大大減少構建時間,幸運的是make直接支持了並行構建的功能(vs沒有提供,鄙視)。在make命令後直接加上”-j”選項就可以了,make會自動開啟多個進程來處理任務,並自動管理這些進程。比如鏈接任務是”$(PROG): foo.o bar.o baz.o”,make會同時開啟3個進程同時編譯foo.o bar.o baz.o,等這些進程都成功結束後再執行後面的鏈接命令。直接加”-j”選項不限定最大的並行任務數量,也可以再後面加上數字來限制它,如”make -j4″表示最多開啟4個並行任務,這個數量寫cpu的核數或其兩倍比較好。個人經驗:限定最大並行數比不限定時,cpu的利用率更高。
Copyright © Linux教程網 All Rights Reserved