1. make 和 Makefile 1.1. 什麼是make? 1.2. 什麼是Makefile? 1.3. make 與 Makefile的關係 2. Makefile的語法 2.1. 基本語法 2.2. 變數 2.3. 偽目標 2.4. 模式規則 2.5. 自動變數 2.6. 條件判斷 3. ...
Linux C++ 開發 系列的前面2篇文章,我們介紹了通過g++
來編譯C++代碼。這對於HelloWorld
程式或者簡單的Demo
程式來說沒有問題,但對於包含多個.cpp和多個.h文件的複雜項目來說,直接用g++
命令來編譯的話,將會使編譯的指令非常冗長且難於維護。這個時候我們可以考慮用makefile來構建我們的程式。
1. make 和 Makefile
1.1. 什麼是make?
make
是一個自動化構建工具,廣泛應用於C/C++項目中,但也可以用於其他編程語言。它的主要功能是根據Makefile
中的規則自動執行一系列命令,從而生成目標文件。make
通過比較目標文件和依賴文件的時間戳來決定是否需要重新構建某個目標,從而避免了不必要的編譯,提高了構建效率。
我們安裝GCC後,應該預設就已經安裝了make,沒有沒有安裝,Ubuntu下可通過如下命令來安裝:
sudo apt update
sudo apt install make
安裝完成後,你可以通過以下命令來驗證make是否安裝成功:
make --version
1.2. 什麼是Makefile?
Makefile 是一個文本文件,定義了構建項目的規則和指令。通常定義了多條包含 目標(target)、依賴(dependency)和命令(command) 的規則。
1.3. make 與 Makefile的關係
- Makefile 你可以理解為是自動構建的腳本,裡面通過 目標(target)、依賴(dependency)和命令(command) 定義了規則,告訴make工具要如何一步步構建我們的最終目標。
- make 是一個命令工具,是一個解釋並執行
Makefile
中指令的命令工具,按照Makefile
制定的規則,構建最終的目標產物。
2. Makefile的語法
2.1. 基本語法
Makefile的基本語法如下:
目標: 依賴
命令
- 目標: 通常是需要生成的文件名,也可以是某個操作(如clean)。
- 依賴: 生成目標文件所依賴的其他文件或其他目標。
- 命令: 生成目標所需執行的shell命令,必須以Tab鍵開頭。
註意
: 命令前面必須是tab鍵,表示命令的開始。不能用4個空格或者兩個空格。
2.2. 變數
在Makefile中,可以使用變數來簡化規則的編寫。變數定義如下:
變數名 = 值
使用變數時,需要在變數名前加上$符號,並用括弧括起來:
$(變數名)
2.3. 偽目標
偽目標是一種特殊的目標,它不代表具體的文件,通常用於執行某些操作。偽目標需要使用.PHONY聲明:
.PHONY: clean
clean:
rm -f hello hello.o
2.4. 模式規則
模式規則允許定義通用的規則,適用於多個目標。例如:
%.o: %.c
$(CC) $(CFLAGS) -c $<
這條規則表示所有.o文件都依賴於對應的.c文件,並且使用相同的編譯命令。
2.5. 自動變數
make提供了一些自動變數,用於簡化命令的編寫:
- $@:表示目標文件名。
- $<:表示第一個依賴文件名。
- $^:表示所有依賴文件名。
2.6. 條件判斷
Makefile支持條件判斷,可以根據不同的條件執行不同的命令:
ifeq ($(DEBUG),1)
CFLAGS += -g
else
CFLAGS += -O2
endif
3. 示例演示
3.1. 編譯HelloWorld
程式
我們用Makefile
來編譯《Linux C++ 開發2 - 編寫、編譯、執行第一個程式》中的Hello world
程式。
Makefile:
# 編譯 demo01.cpp
demo01.out: demo01.cpp
g++ ./demo01.cpp -o demo01.out
# 申明clean為偽目標
.PHONY: clean
# 定義 clean 命令
clean:
rm -f demo01.out
編譯和運行:
# 編譯
make
# 運行
./demo01.out
Hello, world!
3.2. 編譯多文件項目
3.2.1. 項目概述
《C++之迭代器》一文中有一個例子是這樣的:
一個公司有多個部門,每個部門有多個人組成,這些人中有開發人員,有測試人員,和與項目相關的其它人員,其結構如下圖片。
現在要遍歷這個公司的所有開發人員,遍歷這個公司的所有測試人員。
在這篇文章中,我們用迭代器模式實現了這個需求,類的結構圖是這樣的:
詳細代碼參見: https://gitee.com/spencer_luo/iterator
現在我們就以這個項目為例,看看這個項目的makefile需要怎麼寫?
3.2.2. 需求分析
代碼結構如下,有三個.cpp
,兩個.h
,兩個.hpp
。
依賴關係如下:
Iterator(client) --> Enumerator --> Company --> Department --> Person
./iterator
├── Company.cpp
├── Company.h
├── Department.hpp
├── Enumerator.hpp
├── Iterator.cpp
├── Person.cpp
├── Person.h
└── README.md
3.2.3. Makefile V1.0
# 構建的最終目標 Iterator(可執行文件)
Iterator:Iterator.o Company.o Person.o
g++ -o Iterator Iterator.o Company.o Person.o
# 構建目標 Iterator.o
Iterator.o:Iterator.cpp
g++ -c Iterator.cpp
# 構建目標 Company.o
Company.o:Company.cpp
g++ -c Company.cpp
# 構建目標 Person.o
Person.o:Person.cpp
g++ -c Person.cpp
# 申明clean為偽目標
.PHONY: clean
clean:
rm -f *.o Iterator
執行make
進行編譯:
make
g++ -c -o Iterator.o Iterator.cpp
g++ -c -o Company.o Company.cpp
g++ -c -o Person.o Person.cpp
g++ -o Iterator Iterator.o Company.o Person.o
上面的Makefile
大家可能會有一些疑問,這裡對可能存在的疑問做一些解答。
3.2.3.1. 問題一:為什麼沒有頭文件的依賴?
問題描述:
如:編譯
Person.o
時,Person.cpp
是包含了Person.h
的,為什麼這條規則不寫成:Person.o:Person.cpp Person.h g++ -c Person.cpp
問題解答:
這種寫法也是沒有問題的,對於makefile而言,沒有語法錯誤。但是沒有這個必要,《Linux C++ 開發3 - 你寫的Hello world經過哪些過程才被電腦理解和執行?》一文中我們講了在程式預處理階段,預處理器會將所有通過
#include
包含的頭文件替換成真正的內容,所以我們編譯的時候只需要對.cpp進行編譯即可。
3.2.3.2. 問題二:為什麼沒有對.hpp
的規則定義?
問題描述:
為什麼
Department.hpp
、Enumerator.hpp
不需要編譯。
問題解答:
正常,我們創建C++代碼文件的時候,一般會創建兩個文件:
- 一個是頭文件(如:abc.h),用來進行類、函數、常量等的聲明。
- 一個是源文件(如:abc.cpp),用來進行類、函數的定義。
但這樣每次要創建兩個文件,而且要在兩個文件上分別進行聲明和定義,挺麻煩的。於是為了偷懶,對於一些簡單的,沒有交叉引用的類,我們通常會把聲明和定義都放在一個文件中,這個文件通常以
.hpp
作為尾碼(如:abc.hpp)。
.hpp
本質上還是一個頭文件,GCC在編譯的時候,會把它當做頭文件來處理。所以我們在Makefile中可以不用寫對.hpp
的編譯規則。
3.2.4. Makefile V2.0
GNU的make很強大,它可以自動推導文件以及文件依賴關係後面的命令,於是我們就沒必要在每一個.o
文件後都寫上編譯的命令和規則,因為我們的make會自動識別,並自己推導命令。
於是我們的Makefile可以簡化為:
# 構建的最終目標 Iterator(可執行文件)
Iterator:Iterator.o Company.o Person.o
g++ -o Iterator Iterator.o Company.o Person.o
# 申明clean為偽目標
.PHONY: clean
clean:
rm -f *.o Iterator
執行make
命令,我們會看到它會自動先去編譯.o
, 然後再鏈接生成最終的二進位文件,編譯的過程和V1.0是一樣的。
make
g++ -c -o Iterator.o Iterator.cpp
g++ -c -o Company.o Company.cpp
g++ -c -o Person.o Person.cpp
g++ -o Iterator Iterator.o Company.o Person.o
大家好,我是陌塵。
IT從業10年+, 北漂過也深漂過,目前暫定居於杭州,未來不知還會飄向何方。
搞了8年C++,也乾過2年前端;用Python寫過書,也玩過一點PHP,未來還會折騰更多東西,不死不休。
感謝大家的關註,期待與你一起成長。
【SunLogging】 掃碼二維碼,關註微信公眾號,閱讀更多精彩內容