|
前言:編譯命令敲累了?是時候?qū)W點自動化了!每次編譯項目是不是手動敲gcc 命令? 當(dāng)項目文件一多,命令就像繞口令一樣——又長又復(fù)雜,還特別容易出錯。
別怕,今天我就帶你認(rèn)識一個“懶人神器”——Makefile。
用 Makefile 的好處很簡單:
代碼編譯自動化,輕松又高效;不用手動敲命令,少掉坑;項目多大都不怕,它全能搞定。只要學(xué)會寫 Makefile,編譯這種枯燥的事情,再也不用你操心!讓我們從零開始,一步步帶你搞清楚它是啥、怎么寫,看完就能用!
1、什么是 Makefile?Makefile 就是一個編譯指揮官,你把編譯規(guī)則寫在里面,之后用一條簡單的命令make,它就會按照規(guī)則自動完成所有的編譯任務(wù)。
打個比方,你是項目經(jīng)理,Makefile 就是你的筆記本,記錄著項目的“施工計劃”:
每個目標(biāo)(比如可執(zhí)行文件main)的來源(哪些源文件);這些目標(biāo)要用什么命令生成;有哪些需要重復(fù)利用的部分(比如中間文件*.o)。一句話:Makefile 幫你自動化處理那些又多又煩的編譯流程!
2、為什么用 Makefile?假設(shè)你有兩個源文件:main.c 和utils.c,手動編譯步驟大概是這樣:先把main.c 和utils.c 分別編譯成目標(biāo)文件:[/ol]gcc -c main.c -o main.o
gcc -c utils.c -o utils.o 2. 再把目標(biāo)文件鏈接成可執(zhí)行文件:gcc main.o utils.o -o main
看著簡單,但代碼一多,命令就會變成這樣:
gcc -c file1.c -o file1.o
gcc -c file2.c -o file2.o
gcc -c file3.c -o file3.o
...
gcc file1.o file2.o file3.o -o my_program
多打一條命令,多一個機會掉坑;一改代碼,又得全編譯一遍,時間都浪費了。
用 Makefile,只需要:
make
一條命令,全搞定!而且它還會只編譯改動的文件,效率直接起飛。
簡單理解:
沒有 Makefile:自己手敲命令,累。有了 Makefile:只用一句make,剩下的事全自動完成,爽。3、Makefile 的基本結(jié)構(gòu) (一分鐘搞懂)Makefile 是由一組規(guī)則(rule)組成的,每個規(guī)則都包含三部分:目標(biāo)(target):你想要生成的文件,比如main。依賴(dependencies):目標(biāo)文件需要哪些源文件或頭文件。命令(commands):生成目標(biāo)需要運行的命令。[/ol]舉個例子:
main: main.o utils.o
gcc main.o utils.o -o main
main.o: main.c
gcc -c main.c
utils.o: utils.c
gcc -c utils.c
什么意思呢?
上面總共 3 條規(guī)則,來說說第一條規(guī)則:目標(biāo)是main,表示我們要生成一個叫main 的可執(zhí)行文件。依賴是main.o 和utils.o,也就是說生成main 需要這兩個依賴文件先生成,而這兩個依賴是利用規(guī)則 2 和 規(guī)則 3 生成的。命令是gcc main.o utils.o -o main,它負(fù)責(zé)把.o 文件編譯成最終的可執(zhí)行文件。[/ol]后兩條規(guī)則類似,告訴make 怎么生成main.o 和utils.o。
簡單嗎?這就相當(dāng)于告訴 Makefile:“你要先準(zhǔn)備好main.o 和utils.o,然后用 gcc 鏈接它們!
4、Makefile 基礎(chǔ)功能:讓編譯自動化從這里開始4.1 自動生成目標(biāo)文件如果你每次都向上面一樣手寫main.o、utils.o 的生成規(guī)則,那 Makefile 就會變得非常繁瑣和重復(fù)。好消息是,Makefile 支持通配符,可以自動生成規(guī)則!
%.o: %.c
gcc -c $ -o $@
這段代碼怎么用?假設(shè)你有main.c 和utils.c,Makefile 會自動生成對應(yīng)的規(guī)則:
main.o:由main.c 生成,命令是gcc -c main.c -o main.o;utils.o:由utils.c 生成,命令是gcc -c utils.c -o utils.o。解釋一下符號:
%.o 和%.c:% 是通配符,表示文件名匹配,比如main.o 和main.c。$:依賴文件,比如main.c。$@:目標(biāo)文件,比如main.o。用這個規(guī)則,Makefile 直接幫你生成所有目標(biāo)文件,舒服吧?
而當(dāng)項目文件越多,使用 Makefile 的優(yōu)勢就越大。
4.2 增量編譯:只編譯改動的文件Makefile 有個超棒的功能:只編譯需要更新的文件。
它會檢查每個目標(biāo)的依賴文件,如果依賴文件沒有變化,就跳過編譯。
比如你改了main.c,Makefile 只會重新生成main.o,而utils.o 完全不動。
這個功能在項目文件很多的時候,能節(jié)省一大堆時間。
4.3 清理臨時文件編譯后,會留下很多.o 文件和中間文件。Makefile 可以加一個clean 規(guī)則,幫你一鍵清理:
clean:
rm -f *.o main
直接運行make clean,干凈清爽!
5、Makefile 的進階玩法了解了基本用法后,咱們來看一些能提升開發(fā)效率的進階功能。
5.1 基礎(chǔ)玩法 - 提高可讀性和可維護性1. 使用變量:讓 Makefile 更加簡潔變量怎么使用?比如CC = gcc。變量可以讓 Makefile 更加靈活和易維護。
變量的基本用法:
# 定義變量
CC = gcc
CFLAGS = -Wall -g
TARGET = main
SRCS = main.c utils.c
OBJS = $(SRCS:.c=.o)
# $(SRCS:.c=.o) 是 Makefile 中的一種變量替換,它的作用是把變量 SRCS 中的每個 .c 文件名換成對應(yīng)的 .o 文件名。
# 替換之后 OBJS = main.o utils.o
在命令中使用變量時,需要用$() 的形式引用:
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o $(TARGET)
# 使用$()替換變量之后的規(guī)則如下:
main: main.o utils.o
gcc -Wall -g main.o utils.o -o main
這樣,如果你要修改編譯器或優(yōu)化選項,只需要改動變量部分,而不需要手動修改每條規(guī)則。
2、偽目標(biāo):讓 Makefile 更靈活在 Makefile 中,有些目標(biāo)(比如clean)不會生成文件,而是用來執(zhí)行特定的命令,比如清理臨時文件。這種目標(biāo)我們稱為 偽目標(biāo)。
問題來了:如果目錄中剛好有個文件名就叫clean,運行make clean 時,Makefile 會誤以為這個文件已經(jīng)存在,導(dǎo)致規(guī)則不執(zhí)行。
怎么解決?
用.PHONY 聲明偽目標(biāo),告訴make 這個目標(biāo)不是文件,應(yīng)該直接執(zhí)行命令。
示例:聲明偽目標(biāo)
.PHONY: clean
clean:
rm -f $(OBJS) $(TARGET)
這樣,即使目錄中有個名為clean 的文件,make clean 仍然會按規(guī)則執(zhí)行,刪除目標(biāo)文件和中間文件。
記。悍彩遣簧晌募哪繕(biāo),都建議用.PHONY 聲明!
5.2 進階玩法 - 構(gòu)建更強大的 Makefile1、模式規(guī)則:適配更多文件類型有時候我們的項目里,不只有.c 文件,還有.cpp 文件。如果要分別寫規(guī)則,那就太麻煩了!這時候,模式規(guī)則 就能幫上大忙。
什么是模式規(guī)則?
模式規(guī)則就是一種通用規(guī)則,用來告訴 Makefile:
“遇到這種類型的文件,該怎么處理!
比如,告訴 Makefile:
.c 文件用gcc 編譯;.cpp 文件用g++ 編譯。這樣,Makefile 會根據(jù)文件后綴自動選擇正確的規(guī)則,不用你手動一個一個寫。
怎么用?支持 C++ 文件
假設(shè)項目里有.cpp 文件,我們可以加一個模式規(guī)則:
%.o: %.cpp
g++ -c $ -o $@
這樣,Makefile 會自動把所有.cpp 文件編譯成.o 文件,完全不用你操心。
如何同時支持 C 和 C++ 文件?
如果項目里既有.c 文件,也有.cpp 文件,那我們可以寫兩條規(guī)則:
%.o: %.c
$(CC) $(CFLAGS) -c $ -o $@
%.o: %.cpp
g++ -c $ -o $@
這兩條規(guī)則的作用:第一條:告訴 Makefile,.c 文件用gcc 編譯。第二條:告訴 Makefile,.cpp 文件用g++ 編譯。[/ol]這樣,不管你的文件是.c 還是.cpp,Makefile 都會自動搞定。
總結(jié)一下:
它會根據(jù)文件類型,自動選擇合適的編譯方式;你只需要寫一條規(guī)則,Makefile 就能幫你搞定一大堆文件;再也不用重復(fù)寫規(guī)則了,省事又高效!記。何募缶Y不同?用模式規(guī)則全搞定!
2、條件語句:讓 Makefile 更聰明條件語句可以讓 Makefile 根據(jù)實際情況調(diào)整規(guī)則,比如不同的操作系統(tǒng)、不同的編譯模式,用起來既靈活又省心。
1、適配不同平臺
不同操作系統(tǒng)的命令可能不一樣,比如刪除文件,Linux 用rm,Windows 用del。通過條件語句,Makefile 可以自動選擇正確的命令:
OS = $(shell uname)
ifeq ($(OS), Linux)
CLEAN_CMD = rm -f
else
CLEAN_CMD = del
endif
clean:
$(CLEAN_CMD) *.o $(TARGET)
在 Linux 上運行make clean:執(zhí)行rm -f;在 Windows 上運行make clean:執(zhí)行del。這樣,無論在哪個平臺都不用手動改命令了,省事!
2、切換編譯模式
開發(fā)過程中經(jīng)常需要在調(diào)試模式(debug)和發(fā)布模式(release)之間切換:
調(diào)試模式:包含調(diào)試信息(方便排查問題)。發(fā)布模式:優(yōu)化性能(適合生產(chǎn)環(huán)境)。用條件語句很容易實現(xiàn):
ifeq ($(MODE), debug)
CFLAGS = -g -O0
else
CFLAGS = -O2
endif
all:
$(CC) $(CFLAGS) -o $(TARGET) $(SRCS)
運行調(diào)試模式:make MODE=debug
用-g 和-O0 編譯,生成帶調(diào)試信息的程序。
運行發(fā)布模式:make
或:
make MODE=release
用-O2 編譯,生成優(yōu)化后的高性能程序。
小結(jié)一下:
適配不同平臺: 條件語句讓 Makefile 在 Linux 和 Windows 上都能用。切換編譯模式: 方便開發(fā)階段調(diào)試和生產(chǎn)環(huán)境優(yōu)化。3、自動化依賴管理: 讓 Makefile 更聰明!在寫代碼時,.c 文件往往會用到頭文件.h。比如,你的main.c 里可能有一句:
#include "utils.h"
如果有一天你修改了utils.h,Makefile 怎么知道它需要重新編譯main.c 呢?
靠你手動寫依賴規(guī)則?別開玩笑了,項目文件一多,光靠手動寫依賴會把人累趴。
這時候,自動化依賴管理就派上用場了。
什么是自動化依賴管理?
自動化依賴管理的核心是用gcc -M 命令,它能幫你自動生成.c 文件和.h 文件的依賴關(guān)系。每次你修改頭文件時,Makefile 會自動觸發(fā)相關(guān)的.c 文件重新編譯。代碼怎么寫?
看下面這個 Makefile 示例:
# 定義依賴文件列表
DEPS = $(SRCS:.c=.d)
# 生成 .d 文件,寫入依賴規(guī)則
%.d: %.c
$(CC) -M $ > $@
# 包含依賴文件
include $(DEPS)
它到底做了什么?定義依賴文件[/ol]DEPS = $(SRCS:.c=.d)
把源文件列表SRCS 中的每個.c 文件,替換成對應(yīng)的.d 文件,比如:
main.c →main.dutils.c →utils.d這些.d 文件就是用來記錄.c 和.h 之間的關(guān)系。
2. 自動生成依賴規(guī)則
%.d: %.c
$(CC) -M $ > $@
這條規(guī)則會用gcc -M 為每個.c 文件生成一個.d 文件,里面記錄了它依賴哪些頭文件。
比如,如果你的main.c 包含了utils.h,生成的main.d 文件可能是這樣的:
main.o: main.c utils.h3. 包含依賴規(guī)則include $(DEPS)
這句話告訴 Makefile,把所有.d 文件里的內(nèi)容加載進來。每次運行 Makefile 時,它都會檢查.d 文件里的規(guī)則,看哪些文件需要重新編譯。
效果如何?
假設(shè)你有以下文件:
main.c 依賴utils.h;utils.c 不依賴任何頭文件。如果你修改了utils.h,Makefile 會自動發(fā)現(xiàn)這個改動,然后只重新編譯main.c,而不會動utils.c。
自動化依賴管理的好處:再也不用手動寫依賴規(guī)則,讓 Makefile 更智能;每次頭文件更新時,Makefile 自動判斷哪些文件需要重新編譯;即使項目文件多到爆,也能輕松應(yīng)對。[/ol]簡單記住:“有.h 文件,就用gcc -M 自動生成依賴!”
4、多目標(biāo)支持:用 Makefile 管理多個模塊當(dāng)你的項目文件越來越多,甚至分成了多個模塊,比如lib 是核心功能模塊,app 是主程序模塊,光靠一個 Makefile 已經(jīng)很難搞定了。
這時候,聰明的做法是:
每個模塊有自己的 Makefile,單獨管理自己的規(guī)則;用一個主 Makefile 調(diào)度所有模塊,讓項目更清晰、更高效!分模塊的做法:
1. 給每個模塊單獨寫一個 Makefile
比如,在lib 模塊的目錄下,我們寫一個lib/Makefile:
# lib/Makefile
lib.a: lib.o # 定義目標(biāo) lib.a
ar rcs lib.a lib.o # 把 lib.o 打包成靜態(tài)庫 lib.a
lib.o: lib.c # 編譯規(guī)則:生成 lib.o
gcc -c lib.c -o lib.o
lib.a 是靜態(tài)庫,ar rcs 是打包命令。這個 Makefile 只關(guān)心lib 模塊自己的文件,不影響其他模塊。2. 用主 Makefile 調(diào)度所有模塊
主 Makefile 位于項目根目錄,負(fù)責(zé)把所有模塊串起來。它并不關(guān)心每個模塊的具體規(guī)則,而是遞歸調(diào)用每個模塊自己的 Makefile:
# 主 Makefile
SUBDIRS = lib app # 定義模塊目錄
all: $(SUBDIRS) # 主目標(biāo):編譯所有模塊
$(SUBDIRS): # 遞歸調(diào)用每個模塊的 Makefile
$(MAKE) -C $@
clean: # 清理所有模塊
for dir in $(SUBDIRS); do $(MAKE) -C $$dir clean; done
代碼解釋:
1. SUBDIR 是模塊列表:這里定義了項目中的模塊,比如lib 和app。每個模塊目錄都有自己的 Makefile。
2. $(MAKE) -C $@ 是關(guān)鍵:這條命令的意思是切換到指定目錄(-C),然后運行這個目錄里的 Makefile。比如,$(MAKE) -C lib 就是到lib 目錄運行它的 Makefile。
[/ol]3. 遞歸清理 : clean 目標(biāo)會循環(huán)進入每個模塊目錄,調(diào)用它們的clean 規(guī)則。注意$$dir 中的雙$,是為了讓 Makefile 能正確解析。
整體效果:
你可以在主目錄運行make,它會自動編譯所有模塊;運行make clean 時,它會遞歸清理所有模塊的臨時文件;每個模塊的規(guī)則獨立,清晰又方便維護。用主 Makefile 調(diào)度多個模塊的好處:結(jié)構(gòu)清晰:每個模塊的規(guī)則獨立管理,主 Makefile 只負(fù)責(zé)調(diào)度。易于維護:修改或新增模塊時,只需在SUBDIRS 添加對應(yīng)模塊目錄即可。高效遞歸:通過$(MAKE) -C 調(diào)用子目錄的 Makefile,模塊間互不干擾。[/ol]簡單來說:分模塊管理,用主 Makefile 調(diào)度,一切井井有條!
5.3 高階玩法 - 優(yōu)化效率與靈活性1. 并行編譯:提高效率Makefile 的make 命令支持并行執(zhí)行多個規(guī)則,用-j 參數(shù)指定并行任務(wù)數(shù)。
示例:并行編譯
make -j4
這會同時運行最多 4 個任務(wù),充分利用多核 CPU,顯著提升大項目的編譯速度。
2. 自定義函數(shù):復(fù)用邏輯在寫 Makefile 時,如果規(guī)則中有重復(fù)的編譯邏輯,比如把.c 文件編譯成.o 文件,一直重復(fù)寫$(CC) $(CFLAGS) 就很麻煩。這時候,我們可以用自定義函數(shù)來統(tǒng)一管理這些重復(fù)操作,既方便又省事!
定義函數(shù):
用define 和endef 定義一個編譯函數(shù):
define compile
$(CC) $(CFLAGS) -c $ -o $@
endef
compile 是函數(shù)名,表示編譯的邏輯;$ 是依賴文件(比如main.c),$@ 是目標(biāo)文件(比如main.o)。使用函數(shù):
調(diào)用自定義函數(shù)時,用$(call 函數(shù)名):
%.o: %.c
$(call compile)
這條規(guī)則會自動把.c 文件編譯成對應(yīng)的.o 文件。
小結(jié)一下:自定義函數(shù)減少了重復(fù)代碼;修改邏輯時,只需改函數(shù)定義,其他地方不用動;讓 Makefile 簡潔易讀,清晰高效。[/ol]一句話:把重復(fù)的邏輯封裝成函數(shù),Makefile 也能優(yōu)雅起來!
3. 靜態(tài)模式規(guī)則:批量生成目標(biāo)文件當(dāng)多個文件需要用相似的規(guī)則編譯時,一個個寫太麻煩,用靜態(tài)模式規(guī)則 就能一次性搞定!
先來看個簡單示例: 假設(shè)我們要把多個.c 文件編譯成.o 文件:
OBJS = main.o utils.o io.o
$(OBJS): %.o: %.c
$(CC) $(CFLAGS) -c $ -o $@
這是什么意思?
$(OBJS) 是目標(biāo)文件列表,比如main.o、utils.o;%.o: %.c 說明每個.o 文件由對應(yīng)的.c 文件生成;$ 是源文件(如main.c),$@ 是目標(biāo)文件(如main.o)。優(yōu)點:減少重復(fù):一條規(guī)則批量處理,省時省力;自動匹配:文件名自動對應(yīng),無需手動寫每條規(guī)則。[/ol]這里順便提下 通配模式規(guī)則,這兩種模式用法很相似。
對于更簡單的項目,你可以用通配模式規(guī)則來實現(xiàn)類似效果:
%.o: %.c
$(CC) $(CFLAGS) -c $ -o $@
解釋一下:
每個.o 文件由對應(yīng)的.c 文件生成;通配符% 會匹配任意文件名,比如main.c 自動對應(yīng)main.o。靜態(tài)模式規(guī)則 vs. 通配模式規(guī)則
特性靜態(tài)模式規(guī)則通配模式規(guī)則匹配范圍針對特定目標(biāo)列表(如$(OBJS)
)自動匹配所有符合%
的文件靈活性控制更精確,只處理指定的目標(biāo)文件簡單統(tǒng)一,適合全局規(guī)則適用場景文件多、規(guī)則復(fù)雜,特定文件需要特殊處理文件少、規(guī)則統(tǒng)一,簡單項目總結(jié):
通配模式規(guī)則適合簡單項目,一條規(guī)則處理所有文件;靜態(tài)模式規(guī)則適合復(fù)雜項目,可以精確控制哪些文件應(yīng)用規(guī)則。記。汉唵稳钟猛ㄅ洌珳(zhǔn)處理選靜態(tài)!
4. 跨平臺構(gòu)建:用 CMake 生成 Makefile如果項目需要在多個平臺(如 Windows、Linux、macOS)上編譯,直接寫 Makefile 會很麻煩。這時,可以用 CMake 自動生成適配不同平臺的 Makefile。
使用方法:
1. 創(chuàng)建 CMake 配置文件
在項目目錄下新建CMakeLists.txt,內(nèi)容如下:
# 聲明最低版本要求cmake_minimum_required(VERSION 3.10)
# 定義項目名稱project(MyProject)
# 指定可執(zhí)行文件add_executable(main main.c utils.c)2. 生成 Makefile
在終端運行:
cmake .
3. 編譯項目
使用生成的 Makefile:
make
優(yōu)點:
跨平臺:適配 Windows、Linux、macOS 等操作系統(tǒng);簡化管理:無需手寫復(fù)雜的 Makefile。一句話總結(jié):用 CMake 自動生成 Makefile,跨平臺編譯就是這么簡單!
五、完整示例結(jié)合前面學(xué)到的內(nèi)容,來看看一個完整的 Makefile:
# 定義變量
CC = gcc # 編譯器
CFLAGS = -Wall -g # 編譯參數(shù):開啟所有警告和調(diào)試信息
SRCS = $(wildcard *.c) # 獲取當(dāng)前目錄下所有的 .c 文件,并賦值給 SRCS 變量,例如:SRCS = main.c utils.c
OBJS = $(SRCS:.c=.o) # 把 .c 文件替換成 .o 文件,替換之后,OBJS = main.o utils.o
TARGET = main # 最終生成的可執(zhí)行文件
# 編譯規(guī)則
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o $(TARGET)
# 生成 .o 文件規(guī)則
%.o: %.c
$(CC) $(CFLAGS) -c $ -o $@
# 加上 .PHONY 聲明偽目標(biāo)
.PHONY: clean
clean:
rm -f $(OBJS) $(TARGET)
使用:
make:生成 可執(zhí)行文件main;make clean:會刪除所有.o 文件和可執(zhí)行文件main,保持項目目錄干凈。看了這篇文章,相信你看上面的 Makefile 代碼 應(yīng)該很輕松!六、寫在最后:從 Makefile 開始,走向編譯自動化!Makefile 就是編譯中的“懶人神器”,一旦用上,你會發(fā)現(xiàn):
不再手動敲命令,編譯變得更簡單;即使項目越來越大,管理起來也毫不費力;提高效率,節(jié)省時間,輕松搞定復(fù)雜編譯!如果你還在手動敲命令,趕緊試試寫個 Makefile,體驗一下自動化的快樂吧~
下篇文章我們將帶你進入 CMake 的世界,了解如何跨平臺管理項目,敬請期待!
end
一口Linux
關(guān)注,回復(fù)【1024】海量Linux資料贈送
精彩文章合集
文章推薦
?【專輯】ARM?【專輯】粉絲問答?【專輯】所有原創(chuàng)?【專輯】linux入門?【專輯】計算機網(wǎng)絡(luò)?【專輯】Linux驅(qū)動?【干貨】嵌入式驅(qū)動工程師學(xué)習(xí)路線?【干貨】Linux嵌入式所有知識點-思維導(dǎo)圖 |
|