Makefile簡單入門基礎
本文章是參考韋東山第一期ARM裸機加強Makefile相關內容。 參考連結:百問科技wiki教程Makefile
一、gcc編譯過程詳解
1、gcc的使用方法:gcc [選項] 檔名
2、gcc常用選項 一個c/c++檔案要經過預處理、編譯、彙編和連結才能變成可執行檔案。
(1)預處理 C/C++原始檔中,以#開頭的命令被稱為預處理命令,如包含命令#include、巨集定義命令#define、條件編譯命令#if、#ifdef等。預處理就是將要包含(include)的檔案插入原檔案中、將巨集定義展開、根據條件編譯命令選擇要使用的程式碼,最後將這些東西輸出到一個.i檔案中等待進一步處理。
(2)編譯 編譯就是把C/C++程式碼(比如上述的.i檔案)翻譯成彙編程式碼。
(3)彙編 彙編就是將第二步輸出的彙編程式碼翻譯成符合一定格式的機器程式碼,在Linux系統上一般表現為ELF目標檔案(OBJ檔案)。反彙編是指將機器程式碼轉換為彙編程式碼,這在除錯程式時常常用到。
(4)連結 連結就是將上步生成的OBJ檔案和系統庫的OBJ檔案、庫檔案連結起來,最終生成了可以在特定平臺執行的可執行檔案。
hello.c(預處理)->hello.i(編譯)->hello.s(彙編)->hello.o(連結)->hello
詳細的每一步命令如下:
gcc -E -o hello.i hello.c gcc -S -o hello.s hello.i gcc -c -o hello.o hello.s gcc -o hello hello.o
上面一連串命令比較麻煩,gcc會對.c檔案預設進行預處理操作,使用-c再來指明瞭編譯、彙編,從而得到.o檔案, 再將.o檔案進行連結,得到可執行應用程式。簡化如下:
gcc -c -o hello.o hello.c
gcc -o hello hello.o
3、深入講解連結過程
前面編譯出來的可執行檔案比原始碼大了很多,這是什麼原因呢?
我們從連結過程來分析,連結將彙編生成的OBJ檔案、系統庫的OBJ檔案、庫檔案連結起來,crt1.o、crti.o、crtbegin.o、crtend.o、crtn.o這些都是gcc加入的系統標準啟動檔案,它們的加入使最後出來的可執行檔案相原來大了很多。
-lc:連結libc庫檔案,其中libc庫檔案中就實現了printf等函式。
gcc -v -nostdlib -o hello hello.o
會提示因為沒有連結系統標準啟動檔案和標準庫檔案,而連結失敗。
這個-nostdlib選項常用於裸機bootloader、linux核心等程式,因為它們不需要啟動檔案、標準庫檔案。
一般應用程式才需要系統標準啟動檔案和標準庫檔案。 裸機/bootloader、linux核心等程式不需要啟動檔案、標準庫檔案。
動態連結使用動態連結庫進行連結,生成的程式在執行的時候需要載入所需的動態庫才能執行。 動態連結生成的程式體積較小,但是必須依賴所需的動態庫,否則無法執行。
gcc -c -o hello.o hello.c
gcc -o hello_shared hello.o
靜態連結使用靜態庫進行連結,生成的程式包含程式執行所需要的全部庫,可以直接執行, 不過靜態連結生成的程式體積較大。
gcc -c -o hello.o hello.c
gcc -static -o hello_static hello.o
二、Makefile的基礎入門知識
1、Makefile的引入及其規則
我們寫一個程式: a.c:
#include "stdio.h"
void printf_hello_world(void)
{
printf("hello world\r\n");
}
main.c:
#include "stdio.h"
extern void printf_hello_world(void);
int main(void)
{
printf_hello_world();
return 0;
}
編譯:
gcc -o test a.c main.c
執行:
./test
結果:
hello world
gcc -o test a.c mian.c
這條命令雖然簡單,但是它完成的功能不簡單。 我們來看看它做了哪些事情。
我們知道.c程式 --> 得到可執行程式
它們之間要經過四個步驟:
(1).預處理
(2).編譯
(3).彙編
(4).連結
我們經常把前三個步驟統稱為編譯了。我們具體分析:gcc -o test a.c mian.c這條命令 它們要經過下面幾個步驟:
1).對於a.c執行:預處理 編譯 彙編 的過程,a.c -->xxx.s -->xxx.o 檔案。
2).對於mian.c執行:預處理 編譯 彙編 的過程,mian.c -->yyy.s -->yyy.o 檔案。
3).最後:xxx.o和yyy.o連結在一起得到一個test應用程式。
缺點:對所有的檔案都會再處理一次,即使main.c沒有經過修改,main.c也會重新編譯一次, 當檔案比較少時,這沒有沒有什麼問題,當檔案非常多的時候,就會帶來非常多的效率問題。
將編譯和連線分開執行;
編譯:
gcc -o a.o a.c
gcc -o main.o main.c
連結:
gcc -o test a.o main.o
我們想辦法,當我們修改a.c之後,a.c會重現編譯然後再把它們連結在一起就可以了。,mian.c 就不需要重新編譯,那麼如何確定哪個有修改哪沒修改呢?
比較時間:比較a.o和a.c的時間,如果a.c的時間比a.o的時間更加新的話,就表明a.c被修改了,同理main.o和main.c也會進行同樣的比較。比較test和a.o, main.o的時間,如果a.o或者main.o的時間比test更加新的話,就表明應該重新生成test。Makefile 就是這樣做的。
於是我們得出簡單的Makefile規則:
目標 : 依賴1 依賴2 ...
[TAB]命令
按照此規則,我們將相應的編譯連結等命令寫在一個命名為Makefile的檔案:
test:a.o main.o
gcc -o test a.o main.o
a.o : a.c #a.o依賴於a.c,當a.c更加新的話,執行下面的命令來生成a.o
gcc -c -o a.o a.c
main.o : main.c #main.o依賴於main.c,當main.c更加新的話,執行下面的命令,來生成main.o
gcc -c -o main.o main.c
然後執行make
命令就能夠生成test目標檔案。
2、Makefile的語法
萬用字元
%.o:表示所用的.o檔案
%.c:表示所有的.c檔案
[email protected]:表示目標
$<:表示第1個依賴檔案
$^:表示所有依賴檔案
根據這些萬用字元,我們可以將上面的Makefile修改為:
test: a.o main.o
gcc -o test $^
%.o : %.c
gcc -c -o [email protected] $<
下面我們增加一個b.c檔案: b.c:
#include "stdio.h"
void printf_hello_makefile(void)
{
printf("hello makefile\r\n");
}
修改Makefile:
test: a.o b.o main.o
gcc -o test $^
%.o : %.c
gcc -c -o [email protected] $<
執行:
make
結果:
gcc -c -o a.o a.c
gcc -c -o b.o b.c
gcc -c -o main.o main.c
gcc -o test a.o b.o main.o
假想目標: .PHONY
我們想清除檔案,我們在Makefile的結尾新增如下程式碼就可以了:
clean:
rm *.o test
(1).執行make:生成第一個可執行檔案。
(2).執行make clean: 清除所有檔案,即執行:rm *.o test。
make後面可以帶上目標名,也可以不帶,如果不帶目標名的話它就想生成第一個規則裡面的第一個目標。
使用Makefile
執行:make [目標]
也可以不跟目標名,若無目標預設第一個目標。我們直接執行make的時候,會在makefile裡面找到第一個目標然後執行下面的指令生成第一個目標。當我們執行make clean的時候,就會在Makefile裡面找到clean這個目標,然後執行裡面的命令,這個寫法有些問題,原因是我們的目錄裡面沒有clean這個檔案,這個規則執行的條件成立,他就會執行下面的命令來刪除檔案。
如果:該目錄下面有名為clean檔案怎麼辦呢?
我們在該目錄下建立一個名為“clean”的檔案,然後重新執行:make然後make clean,結果(會有下面的提示:):
make: `clean' is up to date.
它根本沒有執行我們的刪除操作,這是為什麼呢?
我們之前說,一個規則能過執行的條件:
(1).目標檔案不存在
(2).依賴檔案比目標新。
現在我們的目錄裡面有名為“clean”的檔案,目標檔案是有的,並且沒有依賴檔案,沒有辦法判斷依賴檔案的時間。這種寫法會導致:有同名的"clean"檔案時,就沒有辦法執行make clean操作。解決辦法:我們需要把目標定義為假想目標,用關鍵字PHONY。
.PHONY: clean //把clean定義為假想目標。他就不會判斷名為“clean”的檔案是否存在
然後在Makfile結尾新增.PHONY: clean語句,重新執行:make clean,就會執行刪除操作。
變數 在makefile中有兩種變數:
1)簡單變數(即時變數):
A := xxx # A的值即刻確定,在定義時即確定
對於即時變數使用“:=”表示,它的值在定義的時候已經被確定了
2)延時變數
B = xxx # B的值使用到時才確定
對於延時變數使用“=”表示。它只有在使用到的時候才確定,在定義/等於時並沒有 確定下來。
想使用變數的時候使用“$”來引用,如果不想看到命令是,可以在命令的前面加上"@"符號,就不會顯示命令本身。當我們執行make命令的時候,make這個指令本身,會把整個Makefile讀進去,進行全部分析,然後解析裡面的變數。常用的變數的定義如下:
:= # 即時變數
= # 延時變數
?= # 延時變數, 如果是第1次定義才起效, 如果在前面該變數已定義則忽略這句
+= # 附加, 它是即時變數還是延時變數取決於前面的定義
?=: #如果這個變數在前面已經被定義了,這句話就不會起效果
例項:
A := $(C)
B = $(C)
C = abc
#D = 100ask
D? = weidongshan
all:
@echo A = $(A)
@echo B = $(B)
@echo D = $(D)
C += 123
執行:
make
結果:
A =
B = abc 123
D = weidongshan
分析:
(1) A := $©: A為即時變數,在定義時即確定,由於剛開始C的值為空,所以A的值也為空。
(2)B = $©: B為延時變數,只有使用到時它的值才確定,當執行make時,會解析Makefile裡面的所用變數,所以先解析C = abc,然後解析C += 123,此時,C = abc 123,當執行:@echo B = $(B) B的值為 abc 123。
(3)D ?= weidongshan: D變數在前面沒有定義,所以D的值為weidongshan,如果在前面新增D = 100ask,最後D的值為100ask。
我們還可以通過命令列存入變數的值 例如:
執行:
make D=123456
裡面的D ?= weidongshan
這句話就不起作用了。
結果:
A =
B = abc 123
D = 123456
三、Makefile函式
makefile 裡面可以包含很多函式,這些函式都是make本身實現的,下面我們來幾個常用的函式。
引用一個函式用“$”。
函式foreach 函式foreach語法如下:
$(foreach var,list,text)
前兩個引數,‘var’和‘list’,將首先擴充套件,注意最後一個引數‘text’此時不擴充套件;接著,對每一個‘list’擴充套件產生的字,將用來為‘var’擴充套件後命名的變數賦值;然後‘text’引用該變數擴充套件;因此它每次擴充套件都不相同。結果是由空格隔開的‘text’ 在‘list’中多次擴充套件的字組成的新的‘list’。‘text’多次擴充套件的字串聯起來,字與字之間由空格隔開,如此就產生了函式foreach的返回值。
例項:
A = a b c
B = $(foreach f, &(A), $(f).o)
all:
@echo B = $(B)
結果:
B = a.o b.o c.o
函式filter/filter-out 函式filter/filter-out語法如下:
$(filter pattern...,text) # 在text中取出符合patten格式的值
$(filter-out pattern...,text) # 在text中取出不符合patten格式的值
例項:
C = a b c d/
D = $(filter %/, $(C))
E = $(filter-out %/, $(C))
all:
@echo D = $(D)
@echo E = $(E)
結果:
D = d/
E = a b c
Wildcard函式 函式Wildcard語法如下:
$(wildcard pattern) # pattern定義了檔名的格式, wildcard取出其中存在的檔案
這個函式wildcard會以pattern這個格式,去尋找存在的檔案,返回存在檔案的名字。
例項:
在該目錄下建立三個檔案:a.c b.c c.c
files = $(wildcard *.c)
all:
@echo files = $(files)
結果:
files = a.c b.c c.c
我們也可以用wildcard函式來判斷,真實存在的檔案 例項:
files2 = a.c b.c c.c d.c e.c abc
files3 = $(wildcard $(files2))
all:
@echo files3 = $(files3)
結果:
files3 = a.c b.c c.c
patsubst函式 函式patsubst語法如下:
$(patsubst pattern,replacement,$(var))
patsubst函式是從var變數裡面取出每一個值,如果這個符合pattern格式,把它替換成replacement格式。
例項:
files2 = a.c b.c c.c d.c e.c abc
dep_files = $(patsubst %.c,%.d,$(files2))
all:
@echo dep_files = $(dep_files)
結果:
dep_files = a.d b.d c.d d.d e.d abc
最後推薦: 學習Makefile書籍:《跟我一起寫Makefile》 學習Makefike參考文獻: a. 百度搜 “gnu make 於鳳昌”。 b. 檢視官方文件: http://www.gnu.org/software/make/manual/