Linux操作系統從入門到實戰(十)Linux開發工具(下)make/Makefile的推導過程與擴展語法

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首先會在當前目錄下搜索名為Makefilemakefile的文件(注意大小寫敏感,推薦統一使用Makefile)。
  • 如果找不到這兩個文件,會直接報錯“沒有規則可制作目標”

在這里插入圖片描述

(2)確定最終目標

找到Makefile后,make會將文件中第一個目標作為“最終目標”。

  • 在上面的例子中,第一個目標是myproc(可執行文件)。
  • 因此make的最終任務就是生成myproc

在這里插入圖片描述

(3) 檢查最終目標是否需要更新

確定最終目標后,make會通過兩個條件判斷是否需要生成/更新myproc

  • myproc不存在:直接執行后續命令生成它;
  • myproc已存在:比較myproc和它的依賴文件myproc.o修改時間
  • 如果myproc.o的修改時間比myproc晚(即myproc.o被更新過),則需要重新生成myproc
  • 反之,若myprocmyproc.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會按照依賴鏈的反向順序執行命令:

  1. 先執行gcc -E myproc.c -o myproc.i:將源文件myproc.c預處理為myproc.i
  2. 再執行gcc -S myproc.i -o myproc.s:將myproc.i編譯為匯編文件myproc.s
  3. 接著執行gcc -c myproc.s -o myproc.o:將myproc.s匯編為目標文件myproc.o
  4. 最后執行gcc myproc.o -o myproc:將myproc.o鏈接為可執行文件myproc

整個過程就像“鏈式反應”——只有前一個中間文件生成后,才能執行下一個步驟。

(6) 遇到錯誤立即停止

在依賴檢查或命令執行過程中,若出現以下情況,make會直接退出并報錯:

  • 某個依賴文件(如myproc.c)不存在;
  • 命令執行失敗(如編譯錯誤,返回非0狀態碼)。

但需要注意:make只負責檢查“依賴是否存在”和“命令是否執行”,不負責檢查命令的語法正確性(比如把gcc寫成g++,make會執行命令但因錯誤退出)。

(7) 核心邏輯:只做“必要的事”

make的高效性體現在“增量編譯”——它只會重新生成“過時”的文件。例如:

  • 若只修改了myproc.c:make會重新生成myproc.imyproc.smyproc.omyproc
  • 若只修改了myproc.s:make只會重新生成myproc.omyproc,無需處理myproc.imyproc.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.cb.cc.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.cb.c 還是 c.c,這條規則都能自動處理,不用一個一個寫!

(2)通配符函數:自動找文件、改名字

還有兩個超實用的工具:

  • wildcard:幫你找出所有符合條件的文件。比如 SRC=$(wildcard *.c),它會自動收集當前文件夾里所有 .c 文件,不管有多少個。
  • patsubst:幫你批量改文件名。比如 OBJ=$(patsubst %.c,%.o,$(SRC)),意思是“把 SRC 里所有 .c 結尾的文件,改成 .o 結尾”。

舉個例子:如果當前有 a.cb.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 會干以下事情:

  1. 自動找出當前文件夾所有 .c 文件(比如 a.cb.c)。
  2. 自動算出需要生成的 .o 文件(a.ob.o)。
  3. 逐個把 .c 編譯成 .o(不用手動寫每個規則)。
  4. 把所有 .o 鏈接成可執行文件 proc
  5. 提供 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

非常感謝您的閱讀,喜歡的話記得三連哦

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/914580.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/914580.shtml
英文地址,請注明出處:http://en.pswp.cn/news/914580.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

NFS磁盤共享

步驟&#xff1a;注意事項?&#xff1a;確保服務端防火墻關閉&#xff0c;或者允許2049端口通信&#xff0c;客戶端需具備讀寫權限。服務器端安裝NFS服務器&#xff1a;sudo apt-get install nfs-kernel-server # Debian/Ubuntu sudo yum install nfs-utils # Ce…

ORA-06413: 連接未打開

System.Data.OracleClient.OracleException:ORA-06413: 連接未打開 oracle 報錯 ORA-06413: 連接未打開 db.Open();的報錯鏈接未打開&#xff0c;System.Data.OracleClient.OracleException HResult0x80131938 MessageORA-06413: 連接未打開 關于ORA-06413錯誤&#xff08;…

【PCIe 總線及設備入門學習專欄 5.1.2 -- PCIe EP core_rst_n 與 app_rst_n】

文章目錄 app_rst_n 和 core_rst_n 的作用1. core_rst_n — PCIe 控制器內部邏輯復位作用控制方式2. app_rst_n — 應用層/用戶邏輯復位作用特點兩者關系圖示:示例流程(Synopsys EP)rst_sync[3] 的作用詳解(復位同步邏輯)為什么使用 rst_sync[3]?圖示說明Synopsys 官方手…

Python初學者筆記第二十期 -- (文件IO)

第29節課 文件IO 在編程中&#xff0c;文件 I/O&#xff08;輸入/輸出&#xff09;允許程序與外部文件進行數據交互。Python 提供了豐富且易用的文件 I/O 操作方法&#xff0c;能讓開發者輕松實現文件的讀取、寫入和修改等操作。 IO交互方向 從硬盤文件 -> 讀取數據 -> 內…

Java JUC包概述

Java 的 java.util.concurrent&#xff08;簡稱 JUC&#xff09;包是 JDK 5 及以后引入的并發編程工具包&#xff0c;旨在解決傳統線程模型&#xff08;如 synchronized、wait/notify&#xff09;的局限性&#xff0c;提供更靈活、高效、可擴展的并發編程組件。它極大簡化了多線…

LeetCode--44.通配符匹配

前言&#xff1a;不知不覺又斷更一天了&#xff0c;其實昨天就把這道題寫得差不多了&#xff0c;只是剛好在力扣里面看見了一種新的解法&#xff0c;本來想寫出來的&#xff0c;但是我把它推到今天了&#xff0c;因為太晚了&#xff0c;但是今天又睡懶覺了&#xff0c;所以我直…

WHAT - 依賴管理工具 CocoaPods

文章目錄1. 什么是 CocoaPods&#xff1f;2. 如何安裝 CocoaPods&#xff1f;(1) 確保已安裝 Ruby&#xff08;macOS 默認自帶&#xff09;(2) 安裝 CocoaPods(3) 驗證安裝3. 在 React Native 項目中使用 CocoaPods(1) 進入 iOS 目錄(2) 初始化 Podfile&#xff08;如果不存在&…

C++ Boost Aiso TCP 網絡聊天(服務端客戶端一體化)

代碼功能說明: 程序模式: 主動連接模式:當用戶指定對端 IP 和端口時,嘗試連接到對端被動監聽模式:當用戶未指定對端 IP 時,等待其他節點連接線程模型: 主線程:處理用戶輸入和消息發送接收線程:后臺接收并顯示對端消息關鍵組件: std::atomic<bool> connected:原…

WeakAuras 5.12.9 Ekkles lua

3.45獵人寶寶狼 技能恢復宏已知3.45BUG RL技能位會清空&#xff0c;小退大退 BB技能全部激活&#xff0c;修復以前可用宏一鍵恢復狀態-------方法一&#xff1a;宏命令---------------------------------------------------------#showtooltip 狂怒之嚎 /petautocaston [btn:1]…

對于編寫PID過程中的問題

當stm32RCT6使用位置環pid控制麥輪轉動一定路程時&#xff0c;在這個時間段內想讓一邊輪胎速度加大應該怎么做&#xff1f;比如我pid的目標脈沖值為9000&#xff0c;在運行到3000的時候車偏左了&#xff0c;那我應該怎樣讓他回正&#xff0c;我想到的辦法是增加其最大的脈沖值&…

LeetCode|Day13|88. 合并兩個有序數組|Python刷題筆記

LeetCode&#xff5c;Day13&#xff5c;88. 合并兩個有序數組&#xff5c;Python刷題筆記 &#x1f5d3;? 本文屬于【LeetCode 簡單題百日計劃】系列 &#x1f449; 點擊查看系列總目錄 >> &#x1f4cc; 題目簡介 題號&#xff1a;88. 合并兩個有序數組 難度&#xf…

【C++】初識C++(1)

個人主頁&#xff1a;我要成為c嘎嘎大王 希望這篇小小文章可以讓你有所收獲&#xff01; 目錄 前言 一、C的第一個程序 二、命名空間 2.1 namespace 的價值 2.2 namespace 的定義 2.2.1 正常的命名空間定義 2.2.2 命名空間可以嵌套 2.2.3 匿名命名空間 2.2.4 同名的name…

在新聞資訊 APP 中添加不同新聞分類頁面,通過 ViewPager2 實現滑動切換

在新聞資訊 APP 中添加不同新聞分類頁面&#xff0c;通過 ViewPager2 實現滑動切換 核心組件的作用 ViewPager2&#xff1a;是 ViewPager 的升級版&#xff0c;基于RecyclerView實現&#xff0c;支持水平 / 垂直滑動、RTL&#xff08;從右到左&#xff09;布局&#xff0c;且修…

vuex操作state為什么要使用mutations作為規范而不是直接修改state

1. 狀態變更的可追蹤性 (Trackable Changes)Devtools 集成&#xff1a;Vue Devtools 可以捕獲每次 mutation 的執行記錄&#xff0c;記錄變更前后的 state 快照、參數和調用棧。直接修改 state&#xff1a;Devtools 無法檢測到變更來源&#xff0c;導致調試困難&#xff08;如無…

Spring AI 系列之九 - RAG-入門

之前做個幾個大模型的應用&#xff0c;都是使用Python語言&#xff0c;后來有一個項目使用了Java&#xff0c;并使用了Spring AI框架。隨著Spring AI不斷地完善&#xff0c;最近它發布了1.0正式版&#xff0c;意味著它已經能很好的作為企業級生產環境的使用。對于Java開發者來說…

【數據結構】基于順序表的通訊錄實現

目錄 1 順序表的概念及結構 1.1 線性表 1.2 順序表分類 1.2.1 靜態順序表 1.2.2 動態順序表 2 順序表的實現 2.1 順序表的初始化 2.2 順序表中數據的增加和修改 2.2.1 順序表的頭插 2.2.2 順序表的尾插 2.2.3 順序表的頭刪 2.2.4 順序表的尾刪 2.2.5 順序表指定位置…

C語言與匯編混合編程

一、GCC 擴展語法與MSVC約束 &#xff08;一&#xff09;GCC&#xff08;GNU Compiler Collection&#xff09;內聯匯編語法 asm("匯編指令");#或者 __asm__("匯編指令");#使用更復雜的語法來指定輸入、輸出操作數和修改的寄存器&#xff1a; asm volatile…

WPF中的ListBox詳解

文章目錄簡介ListBoxItem選中項目動態列表簡介 【ListBox】是列表控件&#xff0c;其內部可包含多個【ListBoxItem】&#xff0c;用戶可以從列表中選擇一個或多個項&#xff0c;若Item個數超過指定高度&#xff0c;則右側會自動出現滾動條&#xff0c;非常便捷。盡管邏輯上來說…

【歷史人物】【李白】生平事跡

目錄 一、李白個人簡歷 二、個人主要經歷 三、個人成就及影響 1、詩 2、詞 3、書法 4、劍術 5、理想 四、歷史評價 五、趣事 1、李白擱筆 2、贈汪倫 一、李白個人簡歷 基本信息? 姓名&#xff1a;李白&#xff0c;字太白&#xff0c;號青蓮居士 性別&#xff1…

HALCON+PCL混合編程

HALCON與PCL的混合編程基礎 HALCON和PCL(Point Cloud Library)都是處理3D數據的強大工具&#xff0c;但它們有著不同的設計目標和數據結構。HALCON專注于機器視覺應用&#xff0c;提供了豐富的圖像處理和分析功能&#xff1b;而PCL則是專門為點云處理設計的開源庫。 要實現兩者…