Linux操作系統從入門到實戰(十)Linux開發工具(下)make/Makefile的推導過程與擴展語法
- 前言
- 一、 make/Makefile的推導過程
- 1. 先看一個完整的Makefile示例
- 2. make的工作流程
- (1)尋找Makefile文件
- (2)確定最終目標
- (3) 檢查最終目標是否需要更新
- (4) 從最終目標到中間文件
- (5) 反向執行命令:從源文件到最終目標
- (6) 遇到錯誤立即停止
- (7) 核心邏輯:只做“必要的事”
- 二、make/Makefile的擴展語法
- 1. 變量的妙用
- 2. 自動變量
- 3. 批量處理
- (1)模式規則:一鍵編譯所有 .c 文件
- (2)通配符函數:自動找文件、改名字
- 4. 高級小技巧
- 5. 完整示例
前言
- 前面的博客里我們講解了Linux開發工具自動化構建-make/Makefile里的基礎知識
- 接下來我們繼續講解Linux開發工具自動化構建-make/Makefile里的細節,make/Makefile的推導過程與擴展語法
我的個人主頁,歡迎來閱讀我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Linux知識文章專欄
歡迎來閱讀指出不足
https://blog.csdn.net/2402_83322742/category_12879535.html?spm=1001.2014.3001.5482
一、 make/Makefile的推導過程
1. 先看一個完整的Makefile示例
假設我們有一個名為myproc
的程序,對應的Makefile內容如下
# 最終目標:生成可執行文件myproc,依賴于中間文件myproc.o
myproc: myproc.ogcc myproc.o -o myproc # 通過鏈接myproc.o生成可執行文件myproc# 中間目標:生成目標文件myproc.o,依賴于匯編文件myproc.s
myproc.o: myproc.sgcc -c myproc.s -o myproc.o # 將匯編文件myproc.s編譯為目標文件myproc.o# 中間目標:生成匯編文件myproc.s,依賴于預處理文件myproc.i
myproc.s: myproc.igcc -S myproc.i -o myproc.s # 將預處理文件myproc.i轉換為匯編文件myproc.s# 中間目標:生成預處理文件myproc.i,依賴于源文件myproc.c
myproc.i: myproc.cgcc -E myproc.c -o myproc.i # 對源文件myproc.c進行預處理,生成myproc.i# 偽目標:清理所有編譯過程中產生的中間文件和可執行文件
.PHONY: clean
clean:rm -f *.i *.s *.o myproc # 刪除所有.i、.s、.o文件及myproc
執行make命令的輸出
- 當我們在終端輸入
make
后,會看到如下執行過程:
$ make
gcc -E myproc.c -o myproc.i # 第一步:預處理
gcc -S myproc.i -o myproc.s # 第二步:生成匯編
gcc -c myproc.s -o myproc.o # 第三步:生成目標文件
gcc myproc.o -o myproc # 第四步:鏈接生成可執行文件
整個過程就像“剝洋蔥”——從最終目標出發,逐層拆解依賴,直到找到最原始的源文件,再反向執行編譯命令。
- 下面我們詳細解釋make是如何一步步完成這個過程的。
2. make的工作流程
在默認情況下(即直接輸入make
命令),工具的執行邏輯可以拆解為以下8個核心步驟:
(1)尋找Makefile文件
- make首先會在當前目錄下搜索名為
Makefile
或makefile
的文件(注意大小寫敏感,推薦統一使用Makefile
)。 - 如果找不到這兩個文件,會直接報錯“沒有規則可制作目標”。
(2)確定最終目標
找到Makefile后,make會將文件中第一個目標作為“最終目標”。
- 在上面的例子中,第一個目標是
myproc
(可執行文件)。 - 因此make的最終任務就是生成
myproc
。
(3) 檢查最終目標是否需要更新
確定最終目標后,make會通過兩個條件判斷是否需要生成/更新myproc
:
- 若
myproc
不存在:直接執行后續命令生成它; - 若
myproc
已存在:比較myproc
和它的依賴文件myproc.o
的修改時間。 - 如果
myproc.o
的修改時間比myproc
晚(即myproc.o
被更新過),則需要重新生成myproc
; - 反之,若
myproc
比myproc.o
新,說明myproc
已是最新,無需操作。
小技巧:可以用
touch 文件名
命令手動更新文件的修改時間(比如touch myproc.o
),測試make是否會重新執行命令。
(4) 從最終目標到中間文件
如果myproc
需要更新(或不存在),make會檢查它的依賴myproc.o
:
- 若
myproc.o
不存在:在Makefile中尋找以myproc.o
為目標的規則(即myproc.o: myproc.s
這一行),然后根據規則生成myproc.o
; - 若
myproc.o
已存在:同樣比較myproc.o
和它的依賴myproc.s
的修改時間,判斷是否需要重新生成myproc.o
。
這個過程會逐層遞歸:
- 檢查
myproc.s
是否存在/需要更新 → 依賴myproc.i
; - 檢查
myproc.i
是否存在/需要更新 → 依賴myproc.c
(源代碼文件)。
直到找到最底層的依賴myproc.c
——這是我們手動編寫的源文件,必須存在(如果myproc.c
缺失,make會直接報錯退出)。
(5) 反向執行命令:從源文件到最終目標
當確認所有依賴都已處理后,make會按照依賴鏈的反向順序執行命令:
- 先執行
gcc -E myproc.c -o myproc.i
:將源文件myproc.c
預處理為myproc.i
; - 再執行
gcc -S myproc.i -o myproc.s
:將myproc.i
編譯為匯編文件myproc.s
; - 接著執行
gcc -c myproc.s -o myproc.o
:將myproc.s
匯編為目標文件myproc.o
; - 最后執行
gcc myproc.o -o myproc
:將myproc.o
鏈接為可執行文件myproc
。
整個過程就像“鏈式反應”——只有前一個中間文件生成后,才能執行下一個步驟。
(6) 遇到錯誤立即停止
在依賴檢查或命令執行過程中,若出現以下情況,make會直接退出并報錯:
- 某個依賴文件(如
myproc.c
)不存在; - 命令執行失敗(如編譯錯誤,返回非0狀態碼)。
但需要注意:make只負責檢查“依賴是否存在”和“命令是否執行”,不負責檢查命令的語法正確性(比如把gcc
寫成g++
,make會執行命令但因錯誤退出)。
(7) 核心邏輯:只做“必要的事”
make的高效性體現在“增量編譯”——它只會重新生成“過時”的文件。例如:
- 若只修改了
myproc.c
:make會重新生成myproc.i
、myproc.s
、myproc.o
和myproc
; - 若只修改了
myproc.s
:make只會重新生成myproc.o
和myproc
,無需處理myproc.i
和myproc.c
; - 若所有文件都未修改:make會直接提示“
myproc
已是最新”,不執行任何命令。
二、make/Makefile的擴展語法
- 剛開始寫 Makefile 時,我們可能會像下面這樣寫
code: code.ogcc code.o -o code # 鏈接:把 .o 變成可執行文件code.o: code.sgcc -c code.s -o code.o # 匯編:.s 變 .ocode.s: code.igcc -S code.i -o code.s # 編譯:.i 變 .scode.i: code.cgcc -E code.c -o code.i # 預處理:.c 變 .iclean:rm -f *.i *.s *.o code # 清理垃圾文件
這看起來還行,但問題大了:
- 如果你把
code.c
改名叫main.c
,上面所有提到code
的地方都得改,漏一個就報錯。 - 要是我們加了個新文件
tool.c
,又得復制粘貼一堆規則,累得慌。
1. 變量的妙用
如果把經常用的文件名、命令起個外號,改的時候只改外號,是不是就方便了?
- 這就是變量的作用。
比如下面這樣:
BIN=code # 給可執行文件起個外號叫 BIN
CC=gcc # 給編譯器起個外號叫 CC
SRC=code.c # 給源文件起個外號叫 SRC
FLAGS=-o # 給輸出參數起個外號叫 FLAGS
RM=rm -f # 給刪除命令起個外號叫 RM$(BIN):$(SRC) # 用外號代替具體名字$(CC) $(FLAGS) $(BIN) $(SRC) # 相當于 gcc -o code code.cclean:$(RM) $(BIN) # 相當于 rm -f code
- 現在如果要改文件名,比如把
code.c
改成main.c
,只需要改SRC=main.c
就行,其他地方不用動。
2. 自動變量
有時候規則里的文件名會重復。
- 比如
gcc -o code code.o
里,code
出現了兩次。要是文件名很長,寫起來超麻煩。這時候“自動變量”就派上用場了,它們能自動代表規則里的目標或依賴文件。
常用的有三個:
$@
:代表當前規則的“目標文件”(比如上面的code
)。$^
:代表當前規則的“所有依賴文件”(比如上面的code.o
)。$<
:代表當前規則的“第一個依賴文件”(比如只有一個依賴時,和$^
一樣)。
舉個例子:
$(BIN):$(OBJ) $(CC) -o $@ $^ # 相當于 gcc -o code code.o($@ 是 code,$^ 是 code.o)@echo "正在把 $^ 變成 $@" # 會打印:正在把 code.o 變成 code%.o:%.c # 后面會講這個,先關注自動變量$(CC) -c $< # 相當于 gcc -c code.c($< 是 code.c)@echo "正在把 $< 變成 $@" # 會打印:正在把 code.c 變成 code.o
是不是像用了“占位符”?不用手寫具體文件名,Makefile 自動幫你填,少寫好多字。
3. 批量處理
如果你的項目有多個 .c
文件(比如 a.c
、b.c
、c.c
),總不能每個都寫一條編譯規則吧?這時候就需要“模式規則”和“通配符”來批量干活。
(1)模式規則:一鍵編譯所有 .c 文件
模式規則用 %
當通配符,比如 %.o: %.c
表示:“所有 .o
文件都由對應的 .c
文件生成”。
%.o: %.c # 只要有 x.c,就自動生成 x.o$(CC) -c $< -o $@ # 對每個 .c 文件執行:gcc -c x.c -o x.o
%.o: %.c # 只要有 x.c,就自動生成 x.o
現在不管你有 a.c
、b.c
還是 c.c
,這條規則都能自動處理,不用一個一個寫!
(2)通配符函數:自動找文件、改名字
還有兩個超實用的工具:
wildcard
:幫你找出所有符合條件的文件。比如SRC=$(wildcard *.c)
,它會自動收集當前文件夾里所有.c
文件,不管有多少個。patsubst
:幫你批量改文件名。比如OBJ=$(patsubst %.c,%.o,$(SRC))
,意思是“把SRC
里所有.c
結尾的文件,改成.o
結尾”。
舉個例子:如果當前有 a.c
、b.c
,那么:
SRC=$(wildcard *.c) # SRC 就等于 "a.c b.c"
OBJ=$(SRC:.c=.o) # 這是上面那句的簡寫,OBJ 就等于 "a.o b.o"
這下不用手動列所有文件了,Makefile 會自動幫你找齊。
4. 高級小技巧
命令前的 @ 和 -
- 命令前加
@
:只顯示命令的結果,不顯示命令本身。比如@echo "編譯中..."
,只會打印“編譯中…”,不會顯示echo "編譯中..."
,看起來更清爽。 - 命令前加
-
:就算命令執行失敗,也繼續往下跑。比如-rm -f *.o
,就算沒有.o
文件,也不會報錯中斷。
5. 完整示例
最后來看一個能應付大多數小項目的 Makefile,我們一步步拆開看:
BIN=proc # 可執行文件名叫 proc
CC=gcc # 用 gcc 編譯
SRC=$(wildcard *.c) # 自動找所有 .c 文件
OBJ=$(SRC:.c=.o) # 把 .c 換成 .o,比如 a.c → a.o
LFLAGS=-o # 鏈接時的輸出參數
FLAGS=-c # 編譯時的參數(-c 表示只編譯不鏈接)
RM=rm -f # 刪除命令# 第一步:鏈接所有 .o 文件,生成可執行文件 proc
$(BIN):$(OBJ) @$(CC) $(LFLAGS) $@ $^ # 相當于 gcc -o proc a.o b.o(自動找所有 .o)@echo "鏈接完成:把 $^ 變成了 $@" # 第二步:編譯每個 .c 文件成 .o 文件
%.o:%.c @$(CC) $(FLAGS) $< # 相當于 gcc -c a.c(自動處理每個 .c)@echo "編譯中:$< → $@"# 聲明偽目標
.PHONY: clean test
clean: # 清理垃圾文件$(RM) $(OBJ) $(BIN) # 刪除所有 .o 和 proc@echo "清理完畢!"test: # 測試變量內容@echo "源文件列表:$(SRC)" @echo "目標文件列表:$(OBJ)"
這個 Makefile 會干以下事情:
- 自動找出當前文件夾所有
.c
文件(比如a.c
、b.c
)。 - 自動算出需要生成的
.o
文件(a.o
、b.o
)。 - 逐個把
.c
編譯成.o
(不用手動寫每個規則)。 - 把所有
.o
鏈接成可執行文件proc
。 - 提供
make clean
清理垃圾,make test
查看文件列表。
以上就是這篇博客的全部內容,下一篇我們將繼續探索Linux的更多精彩內容。
我的個人主頁
歡迎來閱讀我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Linux知識文章專欄
歡迎來閱讀指出不足
https://blog.csdn.net/2402_83322742/category_12879535.html?spm=1001.2014.3001.5482
非常感謝您的閱讀,喜歡的話記得三連哦 |