Makefile學(xué)習(xí)筆記

Makefile學(xué)習(xí)筆記

學(xué)習(xí)Makefile的資料

  1. 《跟我一起寫makefile》
  2. 《GUN make manual》
  3. 《GNU Make項(xiàng)目管理(第三版)》

說(shuō)明:
《跟我一起寫makefile》可以被認(rèn)為是《GUN make manual》的簡(jiǎn)化版或者說(shuō)是學(xué)習(xí)筆記,適合入門學(xué)習(xí)。
《GUN make manual》可以當(dāng)做參考手冊(cè),平時(shí)查閱。
《GNU Make項(xiàng)目管理(第三版)》用于進(jìn)階提升。

1. gcc編譯

Makefile本身并不能編譯代碼,它需要結(jié)合gcc 命令才能實(shí)現(xiàn)編譯的功能。

1.1 編譯和鏈接

編譯

編譯時(shí),編譯器需要的是語(yǔ)法的正確,函數(shù)與變量的聲明的正確。對(duì)于后者,通常需要告訴編譯器頭文件的所在位置(頭文件中應(yīng)該只是聲明,定義應(yīng)該放在 C/C++ 文件中),只要所有的語(yǔ)法正確,編譯器就可以編譯出中間目標(biāo)文件。一般來(lái)說(shuō),每個(gè)源文件都應(yīng)該對(duì)應(yīng)于一個(gè)中間目標(biāo)文件(.o 文件或 .obj 文件)。

鏈接

鏈接時(shí),主要是鏈接函數(shù)和全局變量。所以,我們可以使用這些中間目標(biāo)文件(.o 文件或 .obj 文件)來(lái)鏈接我們的應(yīng)用程序。鏈接器并不管函數(shù)所在的源文件,只管函數(shù)的中間目標(biāo)文件(Object File),在大多數(shù)時(shí)候,由于源文件太多,編譯生成的中間目標(biāo)文件太多,而在鏈接時(shí)需要明顯地指出中間目標(biāo)文件名,這對(duì)于編譯很不方便。所以,我們要給中間目標(biāo)文件打個(gè)包,在 Windows 下這種包叫“庫(kù)文件”(Library File),也就是 .lib文件,在 UNIX 下,是 Archive File,也就是 .a 文件。

總結(jié)

編譯時(shí),源文件首先會(huì)生成中間目標(biāo)文件,再由中間目標(biāo)文件生成執(zhí)行文件。編譯器只檢測(cè)程序語(yǔ)法和函數(shù)、變量是否被聲明。如果函數(shù)未被聲明,編譯器會(huì)給出一個(gè)警告,但可以生成 Object File。而在鏈接程序時(shí),鏈接器會(huì)在所有的 Object File 中找尋函數(shù)的實(shí)現(xiàn),如果找不到,那到就會(huì)報(bào)鏈接錯(cuò)誤碼(Linker Error)。

1.2 gcc編譯選項(xiàng)

對(duì)于編譯源碼,Makefile中會(huì)用到大量的gcc選項(xiàng),所以熟悉gcc編譯選項(xiàng)對(duì)于學(xué)習(xí)Makefile至關(guān)重要。

  1. -c::只激活預(yù)處理,編譯,和匯編,也就生成obj文件
  2. -S:只激活預(yù)處理和編譯,就是指把文檔編譯成為匯編代碼。
  3. -E:只激活預(yù)處理,不生成文檔,需要把他重定向到一個(gè)輸出文檔里。
  4. -o:定制目標(biāo)名稱,缺省的時(shí)候gcc 編譯出來(lái)的文檔是a.out
  5. -ansi:關(guān)閉gnu c中和ansi c不兼容的特性,激活ansi c的專有特性。
  6. -Dmacro:相當(dāng)于C語(yǔ)言中的#define macro
  7. -Dmacro=defn:相當(dāng)于C語(yǔ)言中的#define macro=defn
  8. -Umacro :相當(dāng)于C語(yǔ)言中的#undef macro
  9. -Idir:指定頭文件路徑。
  10. -llibrary:指定庫(kù)
  11. -Ldir:定制編譯的時(shí)候,搜索庫(kù)的路徑。
  12. -g:指示編譯器,在編譯的時(shí)候,產(chǎn)生調(diào)試信息。
  13. -static:此選項(xiàng)將禁止使用動(dòng)態(tài)庫(kù),所以,編譯出來(lái)的東西,一般都很大。
  14. -share:此選項(xiàng)將盡量使用動(dòng)態(tài)庫(kù),所以生成文檔比較小,但是需要系統(tǒng)由動(dòng)態(tài)庫(kù)。
  15. -O0 -O1 -O2 -O3:編譯器的優(yōu)化選項(xiàng)的4個(gè)級(jí)別,-O0表示沒(méi)有優(yōu)化,-O1為缺省值,-O3優(yōu)化級(jí)別最高
  16. -Wall:會(huì)打開(kāi)一些很有用的警告選項(xiàng),建議編譯時(shí)加此選項(xiàng)。
  17. -std:指定C標(biāo)準(zhǔn),如-std=c99使用c99標(biāo)準(zhǔn),-std=gnu99,使用C99 再加上 GNU 的一些擴(kuò)展。

2. Makefile規(guī)則

2.1 書寫規(guī)則

Makefile一般規(guī)則

target ... : prerequisites ...
    command
  1. make 會(huì)比較 targets文件和 prerequisites 文件的修改日期,如果 prerequisites 文件的日期要比 targets 文件的日期要新,或者 target 不存在的話,那么 make 就會(huì)執(zhí)行后續(xù)定義的command命令。
  2. makefile中,目標(biāo)的執(zhí)行語(yǔ)句(command)只能使用tab起始,不能以四個(gè)空格代替。如all、clean的下一行。
  3. 偽目標(biāo):當(dāng)一個(gè)偽目標(biāo)有依賴目標(biāo)時(shí),不但會(huì)執(zhí)行規(guī)則(命令),依賴目標(biāo)也總是會(huì)被決議(執(zhí)行)。

靜態(tài)模式規(guī)則

靜態(tài)模式規(guī)則是這樣一個(gè)規(guī)則:規(guī)則存在多個(gè)目標(biāo),并且不同的目標(biāo)可以根據(jù)目標(biāo)文件的名字來(lái)自動(dòng)構(gòu)造出依賴文件。靜態(tài)模式規(guī)則比多目標(biāo)規(guī)則更通用,它不需要多個(gè)目標(biāo)具有相同的依賴。但是靜態(tài)模式規(guī)則中的依賴文件必須是相類似的而不是完全相同的。
規(guī)則:

<targets ...> : <target-pattern> : <prereq-patterns ...>
    <commands>

例子:

objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@

“目標(biāo)模式”或是“依賴模式”中都應(yīng)該有 % 。

模式規(guī)則(又稱隱含模式規(guī)則)

你可以使用模式規(guī)則來(lái)定義一個(gè)隱含規(guī)則。一個(gè)模式規(guī)則就好像一個(gè)一般的規(guī)則,只是在規(guī)則中,目標(biāo)的定義需要有 % 字符。 % 的意思是表示一個(gè)或多個(gè)任意字符。在依賴目標(biāo)中同樣可以使用 % ,只是依賴目標(biāo)中的 % 的取值,取決于其目標(biāo)。
模式規(guī)則中,至少在規(guī)則的目標(biāo)定義中要包含 % ,否則,就是一般的規(guī)則。

%.o : %.c
    <command ......>

將所有的c文件編譯成目標(biāo)文件。

%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

靜態(tài)模式規(guī)則和隱含模式規(guī)則

Makefile 中,靜態(tài)模式規(guī)則和被定義為隱含規(guī)則的模式規(guī)則都是我們經(jīng)常使用的兩種方式。兩者相同的地方都是用目標(biāo)模式和依賴模式來(lái)構(gòu)建規(guī)則中的文件依賴關(guān)系,兩者不同的地方是 make 在執(zhí)行時(shí)使用它們的時(shí)機(jī)。
隱含規(guī)則可被用在任何和它相匹配的目標(biāo)上,相反的,靜態(tài)模式規(guī)則只能用在規(guī)則中明確指出的那些文件的重建過(guò)程中,不能用在除此之外的任何文件的重建過(guò)程中。

2.2 命令

@的作用

通常, make 會(huì)把其要執(zhí)行的命令行在命令執(zhí)行前輸出到屏幕上。當(dāng)我們?cè)诿钚星坝?@ 字符,那么,這個(gè)命令將不被 make 顯示出來(lái)。

遞歸執(zhí)行make

進(jìn)入指定子目錄,并執(zhí)行子目錄的Makefile文件。

SUBDIRS = foo bar baz
.PHONY: subdirs $(SUBDIRS)
subdirs: $(SUBDIRS)
$(SUBDIRS):
    $(MAKE) -C $@ all
for dir in $(SUBDIRS); 
do $(MAKE) -C $$dir all ;
done

推薦使用第一種,偽目標(biāo)方式。
遞歸make clean:

@for dir in ${SUBDIRS}; do \
    ${MAKE} -C $$dir clean; \
done

2.3 變量

變量在聲明時(shí)需要給予初值,而在使用時(shí),需要給在變量名前加上 $ 符號(hào),但最好用小括號(hào) () 或是大括號(hào) {} 把變量給包括起來(lái)。如果你要使用真實(shí)的$ 字符,那么你需要用 $$ 來(lái)表示。

賦值

= 是最基本的賦值
:= 是覆蓋之前的值
?= 是如果沒(méi)有被賦值過(guò)就賦予等號(hào)后面的值
+= 是添加等號(hào)后面的值

=和:=的區(qū)別

1)=

make會(huì)將整個(gè)makefile展開(kāi)后,再?zèng)Q定變量的值。也就是說(shuō),變量的值將會(huì)是整個(gè)makefile中最后被指定的值??蠢?

x = foo
y = $(x) bar
x = xyz

在上例中,y的值將會(huì)是 xyz bar ,而不是 foo bar 。

2):=

“:=”表示變量的值決定于它在makefile中的位置,而不是整個(gè)makefile展開(kāi)后的最終值。

x := foo
y := $(x) bar
x := xyz

在上例中,y的值將會(huì)是 foo bar ,而不是 xyz bar 。

override 指示符

如果通過(guò)make 的命令行參數(shù)對(duì)某個(gè)變量進(jìn)行了設(shè)置,那么 Makefile 中對(duì)這個(gè)變量的賦值會(huì)被忽略。如果你想在 Makefile 中設(shè)置這類參數(shù)的值,那么,你可以使用“override”指示符。

override <variable>; = <value>;
override <variable>; := <value>;
override <variable>; += <more text>;

環(huán)境變量

make 運(yùn)行時(shí)的系統(tǒng)環(huán)境變量可以在 make 開(kāi)始運(yùn)行時(shí)被載入到 Makefile 文件中,但是如果Makefile 中已定義了這個(gè)變量,或是這個(gè)變量由 make 命令行帶入,那么系統(tǒng)的環(huán)境變量的值將被覆蓋。(如果 make 指定了“-e”參數(shù),那么,系統(tǒng)環(huán)境變量將覆蓋 Makefile 中定義的變量)。
因此,如果我們?cè)诃h(huán)境變量中設(shè)置了 CFLAGS 環(huán)境變量,那么我們就可以在所有的 Makefile 中使用這個(gè)變量了。這對(duì)于我們使用統(tǒng)一的編譯參數(shù)有比較大的好處。如果 Makefile 中定義了 CFLAGS,那么則會(huì)使用 Makefile 中的這個(gè)變量,如果沒(méi)有定義則使用系統(tǒng)環(huán)境變量的值,一個(gè)共性和個(gè)性的統(tǒng)一,很像“全局變量”和“局部變量”的特性。
當(dāng) make 嵌套調(diào)用時(shí)(參見(jiàn)前面的“嵌套調(diào)用”章節(jié)),上層 Makefile 中定義的變量會(huì)以系統(tǒng)環(huán)境變量的方式傳遞到下層的 Makefile 中。當(dāng)然,默認(rèn)情況下,只有通過(guò)命令行設(shè)置的變量會(huì)被傳遞。而定義在文件中的變量,如果要向下層 Makefile 傳遞,則需要使用 exprot 關(guān)鍵字來(lái)聲明。

內(nèi)置變量

在隱含規(guī)則中的命令中,基本上都是使用了一些預(yù)先設(shè)置的變量。你可以在你的 makefile 中改變這些變量的值,或是在 make 的命令行中傳入這些值,或是在你的環(huán)境變量中設(shè)置這些值。

關(guān)于命令的變量

? AR : 函數(shù)庫(kù)打包程序。默認(rèn)命令是 ar
? AS : 匯編語(yǔ)言編譯程序。默認(rèn)命令是 as
? CC : C 語(yǔ)言編譯程序。默認(rèn)命令是 cc
? CXX : C++ 語(yǔ)言編譯程序。默認(rèn)命令是 g++

? CPP : C 程序的預(yù)處理器(輸出是標(biāo)準(zhǔn)輸出設(shè)備)。默認(rèn)命令是 $(CC) –E
? RM : 刪除文件命令。默認(rèn)命令是 rm –f

關(guān)于命令參數(shù)的變量

? ARFLAGS : 函數(shù)庫(kù)打包程序 AR 命令的參數(shù),默認(rèn)值是 rv。
? ASFLAGS : 匯編語(yǔ)言編譯器參數(shù)。(當(dāng)明顯地調(diào)用 .s 或 .S 文件時(shí))
? CFLAGS : C 語(yǔ)言編譯器參數(shù)。
? CXXFLAGS : C++ 語(yǔ)言編譯器參數(shù)。
? CPPFLAGS : C 預(yù)處理器參數(shù)。(C 和 Fortran 編譯器也會(huì)用到)
? LDFLAGS : 鏈接器參數(shù)。(如: ld )

自動(dòng)化變量

所謂自動(dòng)化變量,就是這種變量會(huì)把模式中所定義的一系列的文件自動(dòng)地挨個(gè)取出,直至所有的符合模式的文件都取完了。這種自動(dòng)化變量只應(yīng)出現(xiàn)在規(guī)則的命令中。

  • $@ : 表示規(guī)則中的目標(biāo)文件集。在模式規(guī)則中,如果有多個(gè)目標(biāo),那么, $@ 就是匹配于目標(biāo)中模式定義的集合。
  • $% : 僅當(dāng)目標(biāo)是函數(shù)庫(kù)文件中,表示規(guī)則中的目標(biāo)成員名。例如,如果一個(gè)目標(biāo)是 foo.a(bar.o),那么, $% 就是 bar.o , $@ 就是 foo.a 。如果目標(biāo)不是函數(shù)庫(kù)文件,那么其值為空。
  • $< : 依賴目標(biāo)中的第一個(gè)目標(biāo)名字。如果依賴目標(biāo)是以模式(即 % )定義的,那么 $< 將是符合模式的一系列的文件集。注意,其是一個(gè)一個(gè)取出來(lái)的。
  • $? : 所有比目標(biāo)新的依賴目標(biāo)的集合。以空格分隔。
  • $^ : 所有的依賴目標(biāo)的集合。以空格分隔。如果在依賴目標(biāo)中有多個(gè)重復(fù)的,那個(gè)這個(gè)變量會(huì)去除重復(fù)的依賴目標(biāo),只保留一份。
  • $+ : 這個(gè)變量很像\ $^ ,也是所有依賴目標(biāo)的集合。只是它不去除重復(fù)的依賴目標(biāo)。
  • $* : 這個(gè)變量表示目標(biāo)模式中 % 及其之前的部分。如果目標(biāo)是 dir/a.foo.b ,并且目標(biāo)的模式是 a.%.b ,那么, $* 的值就是 dir/a.foo 。這個(gè)變量對(duì)于構(gòu)造有關(guān)聯(lián)的文件名是比較有較。如果目標(biāo)中沒(méi)有模式的定義,那么 $* 也就不能被推導(dǎo)出,但是,如果目標(biāo)文件的后綴是 make 所識(shí)別的,那么 $* 就是除了后綴的那一部分。例如:如果目標(biāo)是 foo.c ,因?yàn)?.c 是 make 所能識(shí)別的后綴名,所以, $* 的值就是 foo 。這個(gè)特性是 GNU make 的,很有可能不兼容于其它版本的make,所以,你應(yīng)該盡量避免使用 $* ,除非是在隱含規(guī)則或是靜態(tài)模式中。如果目標(biāo)中的后綴是make 所不能識(shí)別的,那么 $* 就是空值。

2.4 Make的運(yùn)行

常用目標(biāo)

? all: 作為 Makefile 的頂層目標(biāo),一般此目標(biāo)作為默認(rèn)的終極目標(biāo)。
? clean: 這個(gè)偽目標(biāo)功能是刪除所有被 make 創(chuàng)建的文件。
? distclean: 同樣類似于偽目標(biāo)“ clean”,但它們所定義的刪除命令所刪除的文件更多,可以包含非 make 創(chuàng)建的文件。例如:編譯之前系統(tǒng)的配置文件、鏈接文件等。
? install: 這個(gè)偽目標(biāo)功能是安裝已編譯好的程序,其實(shí)就是把目標(biāo)執(zhí)行文件拷貝到指定的目標(biāo)中去。
? print: 這個(gè)偽目標(biāo)的功能是例出改變過(guò)的源文件。
? tar: 源程序打包備份,也就是一個(gè) tar 文件。
? dist: 為源文件創(chuàng)建發(fā)布的壓縮包,可以使各種壓縮方式的發(fā)布包。
? TAGS: 創(chuàng)建當(dāng)前目錄下所有源文件的符號(hào)信息(“ tags”)文件,這個(gè)文件可被 vim 使用。
? check 和 test: 這兩個(gè)偽目標(biāo)一般用來(lái)測(cè)試 makefile 的流程。

make命令行選項(xiàng)

-f=file, --file=file, --makefile=file 指定需要執(zhí)行的 makefile

替換變量定義

執(zhí)行make時(shí),一個(gè)含有“ =”的命令行參數(shù)“ V=X”的含義是定義變量“ V”的值為“ X”,并將這個(gè)變量作為make的參數(shù)。這種方式定義的變量會(huì)替代Makefile中的同名變量定義(如果存在,并且在Makefile中沒(méi)有使用指示符“ override” 對(duì)這個(gè)變量進(jìn)行說(shuō)明),這個(gè)過(guò)程被稱之命令行參數(shù)定義覆蓋普通變量定義。

make CFLAGS=-g
make CFLAGS='-g –O2'

注:在參數(shù)中如果包含空格或者shell的特殊字符,則需要將參數(shù)放在引號(hào)中

3. so庫(kù)的鏈接

3.1 運(yùn)行時(shí)動(dòng)態(tài)庫(kù)的鏈接

在執(zhí)行可執(zhí)行文件時(shí),提示:

error while loading shared libraries: libprint.so: cannot open shared object file: No such file or directory

解決:有三種方法

1)將 libprint.so放入系統(tǒng)庫(kù)或用戶庫(kù)目錄下:

sudo cp  libprint.so /usr/local/lib
sudo ldconfig

2)在makefile中添加: -Wl,-rpath

LDFLAGS = -lm -lprint -L$(TOP_PATH)/lib -Wl,-rpath=$(TOP_PATH)/lib

說(shuō)明:
gcc編譯鏈接動(dòng)態(tài)庫(kù)時(shí),很有可能編譯通過(guò)但是執(zhí)行時(shí),找不到動(dòng)態(tài)鏈接庫(kù),那是因?yàn)?L選項(xiàng)指定的路徑只在編譯時(shí)有效,編譯出來(lái)的可執(zhí)行文件不知道-L選項(xiàng)后面的值,當(dāng)然找不到。
解決方法是通過(guò)-Wl,rpath=<your_lib_dir>,使得execute記住鏈接庫(kù)的位置

3)使用LD_LIBRARY_PATH

推薦使用方法一。

3.2 編譯時(shí)鏈接

LDFLAGS

鏈接器參數(shù),如指定庫(kù)位置:

LDFLAGS=-L/usr/lib -L/path/to/your/lib

LIBS

告訴鏈接器要鏈接哪些庫(kù)文件,如:

LIBS = -lpthread -liconv  

最正規(guī)的做法:

LDFLAGS = -L/var/xxx/lib -L/opt/mysql/lib -lmysqlclient -liconv

因?yàn)長(zhǎng)IBS不是makefile的自帶變量,屬于用戶自定義變量。

4. 其它

在makefile最好使用絕對(duì)路徑,而非相對(duì)路徑,如:./ 等。因?yàn)樵谀承┣闆r下容易出錯(cuò)。
例如:-Wl,-rpath=./lib選項(xiàng)
雖然編譯出來(lái)的可執(zhí)行文件test可以運(yùn)行,但如果將test 拷貝到其他目錄,運(yùn)行時(shí)便會(huì)提示:

error while loading shared libraries: libprint.so: cannot open shared object file: No such file or directory

所以正確的做法是使用:-Wl,-rpath=$(TOP_PATH)/lib

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容