1.概念
1.1 什么是makefile
Makefile 是一種文本文件,用于描述軟件項目的構建規則和依賴關系,通常用于自動化軟件構建過程。它包含了一系列規則和指令,告訴構建系統如何編譯和鏈接源代碼文件以生成最終的可執行文件、庫文件或者其他目標文件。
1.2 使用makefile的優點
Makefile 主要用于管理大型項目的構建過程,它可以:
- 自動檢測源代碼文件的變化,只編譯發生變化的文件,以提高構建效率。
- 根據依賴關系自動構建所需的目標文件,無需手動管理編譯順序。
- 支持通過簡單的命令來進行構建、清理和其他任務,提高了項目的可維護性和可重復性。
- 可以輕松地添加自定義的構建規則和操作,滿足特定項目的需求。
Makefile 使用一種稱為 Make 的構建工具來解析和執行其中的規則。Make 工具會根據 Makefile 中定義的規則和依賴關系,確定需要重新構建的目標文件,并執行相應的命令來完成構建過程。
2.演變的過程
2.1 gcc編譯
在我們平常linux下編譯文件的時候通過gcc全部編譯鏈接生成可執行的文件
這樣編譯導致的后果是每次改動某一個代碼,其他文件都要跟著編譯,非常的繁瑣每次,那我們有什么更簡單的方法嗎,有
2.2 gcc單個文件編譯后鏈接
很明顯,大家已經看到了,雖然有一點優化,但不多。這也是不可取的。這時候在linux編譯鏈接的時候就出現了makefile文件。
2.3 Makefile的工作原理
Makefile 的工作原理主要涉及兩個部分:規則(rules)和依賴關系(dependencies)。Makefile 中包含了一系列規則,每個規則描述了如何生成一個或多個目標文件,并指定了生成目標文件所需的依賴文件和生成命令。Make 工具會根據這些規則來自動執行構建過程。
下面是 Makefile 的工作原理的詳細解釋:
1.目標和依賴關系定義:
- Makefile 中的每個規則由一個或多個目標(targets)和其對應的依賴關系(dependencies)組成。目標通常是文件名,代表生成的目標文件或執行的操作。依賴關系是目標文件所依賴的文件列表,通常是源文件或其他目標文件。
- 每個規則的格式通常為
?target: dependenciescommand
target
?是目標文件或操作的名稱,dependencies
?是生成?target
?所需的依賴文件列表,command
?是生成?target
?的命令。
? 檢查文件時間戳:- 在執行 Makefile 時,Make 工具會檢查每個目標文件和其依賴文件的時間戳,以確定哪些文件需要重新生成。
- 如果目標文件不存在,或者其時間戳早于任何一個依賴文件的時間戳,那么 Make 工具會執行生成目標文件的命令。
-
執行生成命令:
- 當確定需要重新生成目標文件時,Make 工具會執行該目標的生成命令。生成命令通常是編譯源文件、鏈接目標文件或執行其他操作的命令。
- Make 工具會按照 Makefile 中規定的順序依次執行每個目標的生成命令,直到所有目標文件都被成功生成。
-
遞歸處理依賴:
- 如果一個目標文件的依賴文件也是其他目標文件,則 Make 工具會遞歸處理這些依賴關系,確保所有依賴文件都被生成。
-
增量構建:
- Make 工具會根據文件的時間戳判斷哪些文件需要重新生成,從而實現增量構建。只有發生了變化的文件及其依賴文件才會重新生成,提高了構建效率。
-
通過這樣的方式,Makefile 提供了一種簡潔而有效的方法來管理項目的構建過程,自動化了源代碼的編譯、鏈接和其他構建操作,使得項目的開發和維護更加高效和可靠。
2.4 初階Makefile文件
- 這個版本的Makefile雖然有了一定的用途,但還是無法解決文件太多的問題。如何解決這一難題呢,我們接著向下看
-
2.5 中階Makefile文件
雖然這個看起來已經比較完善了,但是依舊在文件太多的時候,會出現很多編譯.c的文件,那有沒有什么辦法,讓其不用這么麻煩呢-
2.6 后階Makefile文件
-
CXX = gcc TARGET = aio OBJ = BintrayTree.o Queue.o test.oCXXFLAGS = -c -Wall$(TARGET) : $(OBJ) # $@ 表示目標文件 # $^ 所依賴文件$(CXX) -o $@ $^ # % 是一個通配符,用于匹配任意長度的字符序列 %.o : %.c # $< 第一個依賴文件$(CXX) $(CXXFLAGS) $< -o $@# .PHONY防止出現于clear重名的文件而發生歧義 .PHONY: clear # make clear 執行下面的命令(刪除后綴.o和編譯鏈接后的目標文件) clear:rm -r *.o $(TARGET)
-
這樣我們,其實已經足夠優化了,但是我們任然有可優化的地方,比如,我們可不可不列出鏈接的依賴文件,當然可以的
-
2.7 終極版本
- 到這里,我們就可以很清楚的認識到Makefile的優化過程
CXX = gcc TARGET = aio #將后綴為.c的文件放入windcard中 SRC = $(wildcard *.c) #路徑替換,將SRC中的.c替換為.o OBJ = $(patsubst %.c, %.o, $(SRC))CXXFLAGS = -c -Wall$(TARGET) : $(OBJ) # $@ 表示目標文件 # $^ 所依賴文件$(CXX) -o $@ $^ # % 是一個通配符,用于匹配任意長度的字符序列 %.o : %.c # $< 第一個依賴文件$(CXX) $(CXXFLAGS) $< -o $@# .PHONY防止出現于clear重名的文件而發生歧義 .PHONY: clear # make clear 執行下面的命令(刪除后綴.o和編譯鏈接后的目標文件) clear:rm -r *.o $(TARGET)
3,Makefile的編碼規則 - 我們之前已經有所了解Makefile的編碼規則,但是在這里我還是覺得有必要總體講一下
在編寫 Makefile 時,遵循一些編碼規則可以使其更加清晰、易于維護和跨平臺。下面是一些常見的 Makefile 編碼規則:
1.縮進使用Tab鍵:
Makefile 中命令行必須使用 Tab 鍵進行縮進,而不是空格。這是因為 Make 工具默認使用 Tab 鍵作為命令行的縮進標識,使用空格可能會導致 Make 解析錯誤。
2.目標和依賴關系之間的冒號:
目標(target)和依賴關系(dependencies)之間使用冒號(:)分隔。冒號前面是目標,后面是依賴關系。
3.命令行前面的Tab鍵:
在每個規則中,命令行必須以 Tab 鍵開頭,表示該命令是該規則的執行命令。除了注釋以外,任何其他以 Tab 鍵開頭的行都被視為命令。
4.變量使用大寫字母:
為了與命令和目標區分開,通常將變量使用大寫字母命名。例如:CC = gcc。
5.使用變量代替硬編碼的命令和路徑:
使用變量來代替硬編碼的命令和路徑,使得 Makefile 更加靈活和可移植。例如,將編譯器命令使用變量表示:CC = gcc,然后在規則中使用 $(CC) 來引用。 -
6.使用偽目標:
對于不產生實際文件的操作(如清理、安裝等),使用偽目標(.PHONY)來定義。這樣可以確保即使存在同名文件,也不會誤導 Make 工具。例如:.PHONY: clean。
7.注釋使用 #:
使用 # 符號來添加注釋,使得 Makefile 更具可讀性。注釋可以幫助理解 Makefile 中每個規則的作用。
8.模塊化設計:
將 Makefile 模塊化,分成多個小的 Makefile 文件,然后通過 include 指令引入。這樣可以提高 Makefile 的可維護性和可讀性,減少重復代碼。
9.合理使用條件語句:
可以使用條件語句(如 ifeq、ifdef 等)來根據不同的條件執行不同的規則或命令,以實現更靈活的構建過程。
10.跨平臺兼容性:
考慮到不同平臺下的路徑分隔符和命令格式的差異,編寫具有跨平臺兼容性的 Makefile。可以使用自動化工具或條件語句來處理不同平臺下的差異。遵循這些規則可以使 Makefile 更加規范、易讀和易于維護,有助于提高項目的構建效率和可靠性。
4,每期一問
-
4.1 上期答案
// 計算樹的高度 int getHeight(struct TreeNode* root) {if (root == NULL) {return 0;}int leftHeight = getHeight(root->left);int rightHeight = getHeight(root->right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1; }// 判斷是否是平衡二叉樹的輔助函數 bool isBalancedHelper(struct TreeNode* root) {if (root == NULL) {return true;}int leftHeight = getHeight(root->left);int rightHeight = getHeight(root->right);if (abs(leftHeight - rightHeight) <= 1 && isBalancedHelper(root->left) && isBalancedHelper(root->right)) {return true;}return false; }// 判斷是否是平衡二叉樹 bool isBalanced(struct TreeNode* root) {return isBalancedHelper(root); }
4.2 本期問題. - 力扣(LeetCode)