不懂 Makefile 的程序員還敢寫代碼嗎?
哈嘍大家好,我是小康!
上次我們聊了 GCC、G++ 和 GDB,搞清楚了這些工具在 C/C++ 開發中的用法。有了它們,你已經可以愉快地編譯、調試代碼了。
但我問你,每次編譯項目是不是手動敲gcc
命令? 當項目文件一多,命令就像繞口令一樣——又長又複雜,還特別容易出錯。
別怕,今天我就帶你認識一個 “懶人神器”——Makefile。
用 Makefile 的好處很簡單:
-
代碼編譯自動化,輕鬆又高效;
-
不用手動敲命令,少掉坑;
-
項目多大都不怕,它全能搞定。
只要學會寫 Makefile,編譯這種枯燥的事情,再也不用你操心!讓我們從零開始,一步步帶你搞清楚它是啥、怎麼寫,看完就能用!
1、什麼是 Makefile?
Makefile 就是一個編譯指揮官,你把編譯規則寫在裏面,之後用一條簡單的命令make
,它就會按照規則自動完成所有的編譯任務。
打個比方,你是項目經理,Makefile 就是你的筆記本,記錄着項目的 “施工計劃”:
-
每個目標(比如可執行文件
main
)的來源(哪些源文件); -
這些目標要用什麼命令生成;
-
有哪些需要重複利用的部分(比如中間文件
*.o
)。
一句話:Makefile 幫你自動化處理那些又多又煩的編譯流程!
2、爲什麼用 Makefile?
假設你有兩個源文件:main.c
和utils.c
,手動編譯步驟大概是這樣:
- 先把
main.c
和utils.c
分別編譯成目標文件:
gcc -c main.c -o main.o
gcc -c utils.c -o utils.o
- 再把目標文件鏈接成可執行文件:
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 的基本結構 (一分鐘搞懂)
Makefile 是由一組規則(rule)組成的,每個規則都包含三部分:
-
目標(target):你想要生成的文件,比如
main
。 -
依賴(dependencies):目標文件需要哪些源文件或頭文件。
-
命令(commands):生成目標需要運行的命令。
舉個例子:
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 條規則,來說說第一條規則:
-
目標是
main
,表示我們要生成一個叫main
的可執行文件。 -
依賴是
main.o
和utils.o
,也就是說生成main
需要這兩個依賴文件先生成,而這兩個依賴是利用規則 2 和 規則 3 生成的。 -
命令是
gcc main.o utils.o -o main
,它負責把.o
文件編譯成最終的可執行文件。
後兩條規則類似,告訴make
怎麼生成main.o
和utils.o
。
簡單嗎?這就相當於告訴 Makefile:“你要先準備好main.o
和utils.o
,然後用 gcc 鏈接它們。”
4、Makefile 基礎功能:讓編譯自動化從這裏開始
4.1 自動生成目標文件
如果你每次都向上面一樣手寫main.o
、utils.o
的生成規則,那 Makefile 就會變得非常繁瑣和重複。好消息是,Makefile 支持通配符,可以自動生成規則!
%.o: %.c
gcc -c $< -o $@
這段代碼怎麼用?假設你有main.c
和utils.c
,Makefile 會自動生成對應的規則:
-
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
。 -
$@
:目標文件,比如main.o
。
用這個規則,Makefile 直接幫你生成所有目標文件,舒服吧?
而當項目文件越多,使用 Makefile 的優勢就越大。
4.2 增量編譯:只編譯改動的文件
Makefile 有個超棒的功能:只編譯需要更新的文件。
它會檢查每個目標的依賴文件,如果依賴文件沒有變化,就跳過編譯。
比如你改了main.c
,Makefile 只會重新生成main.o
,而utils.o
完全不動。
這個功能在項目文件很多的時候,能節省一大堆時間。
4.3 清理臨時文件
編譯後,會留下很多.o
文件和中間文件。Makefile 可以加一個clean
規則,幫你一鍵清理:
clean:
rm -f *.o main
直接運行make clean
,乾淨清爽!
5、Makefile 的進階玩法
瞭解了基本用法後,咱們來看一些能提升開發效率的進階功能。
5.1 基礎玩法 - 提高可讀性和可維護性
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 文件名換成對應的 .o 文件名。
# 替換之後 OBJS = main.o utils.o
在命令中使用變量時,需要用$()
的形式引用:
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o $(TARGET)
# 使用$()替換變量之後的規則如下:
main: main.o utils.o
gcc -Wall -g main.o utils.o -o main
這樣,如果你要修改編譯器或優化選項,只需要改動變量部分,而不需要手動修改每條規則。
2、僞目標:讓 Makefile 更靈活
在 Makefile 中,有些目標(比如clean
)不會生成文件,而是用來執行特定的命令,比如清理臨時文件。這種目標我們稱爲 僞目標。
問題來了:如果目錄中剛好有個文件名就叫clean
,運行make clean
時,Makefile 會誤以爲這個文件已經存在,導致規則不執行。
怎麼解決?
用.PHONY
聲明僞目標,告訴make
這個目標不是文件,應該直接執行命令。
示例:聲明僞目標
.PHONY: clean
clean:
rm -f $(OBJS) $(TARGET)
這樣,即使目錄中有個名爲clean
的文件,make clean
仍然會按規則執行,刪除目標文件和中間文件。
記住:凡是不生成文件的目標,都建議用.PHONY
聲明!
5.2 進階玩法 - 構建更強大的 Makefile
1、模式規則:適配更多文件類型
有時候我們的項目裏,不只有.c
文件,還有.cpp
文件。如果要分別寫規則,那就太麻煩了!這時候,模式規則 就能幫上大忙。
什麼是模式規則?
模式規則就是一種通用規則,用來告訴 Makefile:
“遇到這種類型的文件,該怎麼處理。”
比如,告訴 Makefile:
-
.c
文件用gcc
編譯; -
.cpp
文件用g++
編譯。
這樣,Makefile 會根據文件後綴自動選擇正確的規則,不用你手動一個一個寫。
怎麼用?支持 C++ 文件
假設項目裏有.cpp
文件,我們可以加一個模式規則:
%.o: %.cpp
g++ -c $< -o $@
這樣,Makefile 會自動把所有.cpp
文件編譯成.o
文件,完全不用你操心。
如何同時支持 C 和 C++ 文件?
如果項目裏既有.c
文件,也有.cpp
文件,那我們可以寫兩條規則:
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
%.o: %.cpp
g++ -c $< -o $@
這兩條規則的作用:
-
第一條:告訴 Makefile,
.c
文件用gcc
編譯。 -
第二條:告訴 Makefile,
.cpp
文件用g++
編譯。
這樣,不管你的文件是.c
還是.cpp
,Makefile 都會自動搞定。
總結一下:
-
它會根據文件類型,自動選擇合適的編譯方式;
-
你只需要寫一條規則,Makefile 就能幫你搞定一大堆文件;
-
再也不用重複寫規則了,省事又高效!
記住:文件後綴不同?用模式規則全搞定!
2、條件語句:讓 Makefile 更聰明
條件語句可以讓 Makefile 根據實際情況調整規則,比如不同的操作系統、不同的編譯模式,用起來既靈活又省心。
1、適配不同平臺
不同操作系統的命令可能不一樣,比如刪除文件,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
:執行rm -f
; -
在 Windows 上運行
make clean
:執行del
。
這樣,無論在哪個平臺都不用手動改命令了,省事!
2、切換編譯模式
開發過程中經常需要在調試模式(debug)和發佈模式(release)之間切換:
-
調試模式:包含調試信息(方便排查問題)。
-
發佈模式:優化性能(適合生產環境)。
用條件語句很容易實現:
ifeq ($(MODE), debug)
CFLAGS = -g -O0
else
CFLAGS = -O2
endif
all:
$(CC) $(CFLAGS) -o $(TARGET) $(SRCS)
- 運行調試模式:
make MODE=debug
用-g
和-O0
編譯,生成帶調試信息的程序。
- 運行發佈模式:
make
或:
make MODE=release
用-O2
編譯,生成優化後的高性能程序。
小結一下:
-
適配不同平臺: 條件語句讓 Makefile 在 Linux 和 Windows 上都能用。
-
切換編譯模式: 方便開發階段調試和生產環境優化。
3、自動化依賴管理: 讓 Makefile 更聰明!
在寫代碼時,.c
文件往往會用到頭文件.h
。比如,你的main.c
裏可能有一句:
#include "utils.h"
如果有一天你修改了utils.h
,Makefile 怎麼知道它需要重新編譯main.c
呢?
靠你手動寫依賴規則?別開玩笑了,項目文件一多,光靠手動寫依賴會把人累趴。
這時候,自動化依賴管理就派上用場了。
什麼是自動化依賴管理?
- 自動化依賴管理的核心是用
gcc -M
命令,它能幫你自動生成.c
文件和.h
文件的依賴關係。每次你修改頭文件時,Makefile 會自動觸發相關的.c
文件重新編譯。
代碼怎麼寫?
看下面這個 Makefile 示例:
# 定義依賴文件列表
DEPS = $(SRCS:.c=.d)
# 生成 .d 文件,寫入依賴規則
%.d: %.c
$(CC) -M $< > $@
# 包含依賴文件
include $(DEPS)
它到底做了什麼?
- 定義依賴文件
DEPS = $(SRCS:.c=.d)
把源文件列表SRCS
中的每個.c
文件,替換成對應的.d
文件,比如:
-
main.c
→main.d
-
utils.c
→utils.d
這些.d
文件就是用來記錄.c
和.h
之間的關係。
- 自動生成依賴規則
%.d: %.c
$(CC) -M $< > $@
這條規則會用gcc -M
爲每個.c
文件生成一個.d
文件,裏面記錄了它依賴哪些頭文件。
比如,如果你的main.c
包含了utils.h
,生成的main.d
文件可能是這樣的:
main.o: main.c utils.h
- 包含依賴規則
include $(DEPS)
這句話告訴 Makefile,把所有.d
文件裏的內容加載進來。每次運行 Makefile 時,它都會檢查.d
文件裏的規則,看哪些文件需要重新編譯。
效果如何?
假設你有以下文件:
-
main.c
依賴utils.h
; -
utils.c
不依賴任何頭文件。
如果你修改了utils.h
,Makefile 會自動發現這個改動,然後只重新編譯main.c
,而不會動utils.c
。
自動化依賴管理的好處:
-
再也不用手動寫依賴規則,讓 Makefile 更智能;
-
每次頭文件更新時,Makefile 自動判斷哪些文件需要重新編譯;
-
即使項目文件多到爆,也能輕鬆應對。
簡單記住:“有.h
文件,就用gcc -M
自動生成依賴!”
4、多目標支持:用 Makefile 管理多個模塊
當你的項目文件越來越多,甚至分成了多個模塊,比如lib
是核心功能模塊,app
是主程序模塊,光靠一個 Makefile 已經很難搞定了。
這時候,聰明的做法是:
-
每個模塊有自己的 Makefile,單獨管理自己的規則;
-
用一個主 Makefile 調度所有模塊,讓項目更清晰、更高效!
分模塊的做法:
1. 給每個模塊單獨寫一個 Makefile
比如,在lib
模塊的目錄下,我們寫一個lib/Makefile
:
# lib/Makefile
lib.a: lib.o # 定義目標 lib.a
ar rcs lib.a lib.o # 把 lib.o 打包成靜態庫 lib.a
lib.o: lib.c # 編譯規則:生成 lib.o
gcc -c lib.c -o lib.o
-
lib.a 是靜態庫,
ar rcs
是打包命令。 -
這個 Makefile 只關心
lib
模塊自己的文件,不影響其他模塊。
2. 用主 Makefile 調度所有模塊
主 Makefile 位於項目根目錄,負責把所有模塊串起來。它並不關心每個模塊的具體規則,而是遞歸調用每個模塊自己的 Makefile:
# 主 Makefile
SUBDIRS = lib app # 定義模塊目錄
all: $(SUBDIRS) # 主目標:編譯所有模塊
$(SUBDIRS): # 遞歸調用每個模塊的 Makefile
$(MAKE) -C $@
clean: # 清理所有模塊
for dir in $(SUBDIRS); do $(MAKE) -C $$dir clean; done
代碼解釋:
-
SUBDIR 是模塊列表:這裏定義了項目中的模塊,比如
lib
和app
。每個模塊目錄都有自己的 Makefile。 -
$(MAKE) -C $@ 是關鍵:這條命令的意思是切換到指定目錄(
-C
),然後運行這個目錄裏的 Makefile。比如,$(MAKE) -C lib
就是到lib
目錄運行它的 Makefile。 -
遞歸清理 : clean 目標會循環進入每個模塊目錄,調用它們的
clean
規則。注意$$dir
中的雙$
,是爲了讓 Makefile 能正確解析。
整體效果:
-
你可以在主目錄運行
make
,它會自動編譯所有模塊; -
運行
make clean
時,它會遞歸清理所有模塊的臨時文件; -
每個模塊的規則獨立,清晰又方便維護。
用主 Makefile 調度多個模塊的好處:
-
結構清晰:每個模塊的規則獨立管理,主 Makefile 只負責調度。
-
易於維護:修改或新增模塊時,只需在
SUBDIRS
添加對應模塊目錄即可。 -
高效遞歸:通過
$(MAKE) -C
調用子目錄的 Makefile,模塊間互不干擾。
簡單來說:分模塊管理,用主 Makefile 調度,一切井井有條!
5.3 高階玩法 - 優化效率與靈活性
1. 並行編譯:提高效率
Makefile 的make
命令支持並行執行多個規則,用-j
參數指定並行任務數。
示例:並行編譯
make -j4
這會同時運行最多 4 個任務,充分利用多核 CPU,顯著提升大項目的編譯速度。
2. 自定義函數:複用邏輯
在寫 Makefile 時,如果規則中有重複的編譯邏輯,比如把.c
文件編譯成.o
文件,一直重複寫$(CC) $(CFLAGS)
就很麻煩。這時候,我們可以用自定義函數來統一管理這些重複操作,既方便又省事!
定義函數:
用define
和endef
定義一個編譯函數:
define compile
$(CC) $(CFLAGS) -c $< -o $@
endef
-
compile
是函數名,表示編譯的邏輯; -
$<
是依賴文件(比如main.c
),$@
是目標文件(比如main.o
)。
使用函數:
調用自定義函數時,用$(call 函數名)
:
%.o: %.c
$(call compile)
這條規則會自動把.c
文件編譯成對應的.o
文件。
小結一下:
-
自定義函數減少了重複代碼;
-
修改邏輯時,只需改函數定義,其他地方不用動;
-
讓 Makefile 簡潔易讀,清晰高效。
一句話:把重複的邏輯封裝成函數,Makefile 也能優雅起來!
3. 靜態模式規則:批量生成目標文件
當多個文件需要用相似的規則編譯時,一個個寫太麻煩,用靜態模式規則 就能一次性搞定!
先來看個簡單示例: 假設我們要把多個.c
文件編譯成.o
文件:
OBJS = main.o utils.o io.o
$(OBJS): %.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
這是什麼意思?
-
$(OBJS)
是目標文件列表,比如main.o
、utils.o
; -
%.o: %.c
說明每個.o
文件由對應的.c
文件生成; -
$<
是源文件(如main.c
),$@
是目標文件(如main.o
)。
優點:
-
減少重複:一條規則批量處理,省時省力;
-
自動匹配:文件名自動對應,無需手動寫每條規則。
這裏順便提下 通配模式規則,這兩種模式用法很相似。
對於更簡單的項目,你可以用通配模式規則來實現類似效果:
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
解釋一下:
-
每個
.o
文件由對應的.c
文件生成; -
通配符
%
會匹配任意文件名,比如main.c
自動對應main.o
。
靜態模式規則 vs. 通配模式規則
總結:
-
通配模式規則適合簡單項目,一條規則處理所有文件;
-
靜態模式規則適合複雜項目,可以精確控制哪些文件應用規則。
記住:簡單全局用通配,精準處理選靜態!
4. 跨平臺構建:用 CMake 生成 Makefile
如果項目需要在多個平臺(如 Windows、Linux、macOS)上編譯,直接寫 Makefile 會很麻煩。這時,可以用 CMake 自動生成適配不同平臺的 Makefile。
使用方法:
1. 創建 CMake 配置文件
在項目目錄下新建CMakeLists.txt
,內容如下:
# 聲明最低版本要求
cmake_minimum_required(VERSION 3.10)
# 定義項目名稱
project(MyProject)
# 指定可執行文件
add_executable(main main.c utils.c)
2. 生成 Makefile
在終端運行:
cmake .
3. 編譯項目
使用生成的 Makefile:
make
優點:
-
跨平臺:適配 Windows、Linux、macOS 等操作系統;
-
簡化管理:無需手寫複雜的 Makefile。
一句話總結:用 CMake 自動生成 Makefile,跨平臺編譯就是這麼簡單!
五、完整示例
結合前面學到的內容,來看看一個完整的 Makefile:
# 定義變量
CC = gcc # 編譯器
CFLAGS = -Wall -g # 編譯參數:開啓所有警告和調試信息
SRCS = $(wildcard *.c) # 獲取當前目錄下所有的 .c 文件,並賦值給 SRCS 變量,例如:SRCS = main.c utils.c
OBJS = $(SRCS:.c=.o) # 把 .c 文件替換成 .o 文件,替換之後,OBJS = main.o utils.o
TARGET = main # 最終生成的可執行文件
# 編譯規則
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o $(TARGET)
# 生成 .o 文件規則
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 加上 .PHONY 聲明僞目標
.PHONY: clean
clean:
rm -f $(OBJS) $(TARGET)
使用:
-
make
:生成 可執行文件main
; -
make clean
:會刪除所有.o
文件和可執行文件main
,保持項目目錄乾淨。
看了這篇文章,相信你看上面的 Makefile 代碼 應該很輕鬆!
六、寫在最後:從 Makefile 開始,走向編譯自動化!
Makefile 就是編譯中的 “懶人神器”,一旦用上,你會發現:
-
不再手動敲命令,編譯變得更簡單;
-
即使項目越來越大,管理起來也毫不費力;
-
提高效率,節省時間,輕鬆搞定複雜編譯!
如果你還在手動敲命令,趕緊試試寫個 Makefile,體驗一下自動化的快樂吧~
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/u1ftbl3MEhEys0o5dpS1yg