Makefile 入門與實踐指南


在這里插入圖片描述

Makefile 是用于 make 工具的配置文件,它定義了如何編譯和鏈接你的項目,讓構建過程自動化。


一、核心概念

make 的核心思想是 “目標”(Target)“依賴”(Dependencies)

  • 目標 (Target):你想要生成的文件(比如可執行文件 myapp)或者一個偽目標(比如 clean)。
  • 依賴 (Dependencies):生成這個目標所必須的文件(比如源代碼 .cpp 文件、頭文件 .h 文件)。
  • 命令 (Commands):當依賴文件比目標文件更新(或目標不存在)時,需要執行的 shell 命令(比如 g++ -c main.cpp)。

基本語法:

目標: 依賴1 依賴2 ...
<TAB> 命令1
<TAB> 命令2
...

關鍵點:

  • 命令前面必須是 TAB,不能是空格!這是 Makefile 最常見的錯誤之一。
  • make 會檢查依賴文件的時間戳。如果任何一個依賴文件比目標文件新,make 就會執行對應的命令來更新目標。

二、一個簡單的例子

假設你有一個 C++ 項目,包含兩個源文件:

  • main.cpp
  • utils.cpp
  • utils.h (被 main.cpputils.cpp 包含)

目標: 生成一個名為 myapp 的可執行文件。

Step 1: 最簡單的 Makefile
# Makefile# 目標: myapp
# 依賴: main.o 和 utils.o (編譯后的目標文件)
# 命令: 鏈接兩個 .o 文件生成 myapp
myapp: main.o utils.og++ main.o utils.o -o myapp# 目標: main.o
# 依賴: main.cpp 和 utils.h (因為 main.cpp 包含了它)
# 命令: 編譯 main.cpp 生成 main.o
main.o: main.cpp utils.hg++ -c main.cpp -o main.o# 目標: utils.o
# 依賴: utils.cpp 和 utils.h
# 命令: 編譯 utils.cpp 生成 utils.o
utils.o: utils.cpp utils.hg++ -c utils.cpp -o utils.o# 偽目標: clean (清理編譯產物)
# .PHONY 告訴 make 這不是一個真實文件,避免和同名文件沖突
.PHONY: clean
clean:rm -f myapp main.o utils.o# 偽目標: all (默認目標,通常放在最前面)
# 當你只輸入 'make' 時,會執行這個目標
.PHONY: all
all: myapp

如何使用:

  1. 將以上內容保存為 Makefile(注意大小寫和無后綴)。
  2. 在終端中,進入包含 Makefile 和源代碼的目錄。
  3. 輸入 makemake 會找到 all 目標,并執行 myapp 目標,從而編譯整個項目。
  4. 輸入 make clean 可以刪除編譯生成的文件。

三、使用變量讓 Makefile 更靈活

直接在命令里寫編譯器和選項很不方便。我們可以用變量。

# Makefile - 使用變量# 定義變量
CC = g++                 # 編譯器
CFLAGS = -Wall -g        # 編譯選項: -Wall 顯示所有警告, -g 生成調試信息
LDFLAGS =                # 鏈接選項 (這里為空):鏈接動靜態庫
TARGET = myapp           # 最終可執行文件名
OBJS = main.o utils.o    # 所有目標文件# 鏈接目標
$(TARGET): $(OBJS)$(CC) $(LDFLAGS) $(OBJS) -o $(TARGET)# 編譯目標文件 (依賴關系保持不變)
main.o: main.cpp utils.h$(CC) $(CFLAGS) -c main.cpp -o main.outils.o: utils.cpp utils.h$(CC) $(CFLAGS) -c utils.cpp -o utils.o# 清理
.PHONY: clean
clean:rm -f $(TARGET) $(OBJS)

現在,如果你想換編譯器(比如用 clang++),只需要修改 CC = clang++ 這一行。


四、使用自動推導規則 (Implicit Rules)

make 內置了一些常識性的規則。例如,它知道如何從 .cpp 文件生成 .o 文件。

我們可以利用這一點,簡化編譯規則:

# Makefile - 使用內置規則CC = g++
CFLAGS = -Wall -g
TARGET = myapp
# 注意: 這里 OBJS 仍然需要明確列出,因為 make 不知道你的源文件是哪些
OBJS = main.o utils.o.PHONY: all
all: $(TARGET)$(TARGET): $(OBJS)$(CC) $(LDFLAGS) $(OBJS) -o $(TARGET)# 刪除了 main.o 和 utils.o 的顯式規則!
# make 會自動使用內置規則: $(CC) $(CFLAGS) -c source.cpp -o source.o.PHONY: clean
clean:rm -f $(TARGET) $(OBJS)

只要 CFLAGS 設置正確,make 就會自動用 g++ -Wall -g -c main.cpp -o main.o 這樣的命令。


五、自動依賴生成 (推薦)

手動維護頭文件依賴(如 main.o: main.cpp utils.h)非常容易出錯且麻煩。我們可以讓編譯器幫我們生成。

# Makefile - 自動依賴生成CC = g++
CFLAGS = -Wall -g
TARGET = myapp
SRCS = main.cpp utils.cpp    # 源文件列表
OBJS = $(SRCS:.cpp=.o)       # 將 .cpp 替換為 .o, 得到 main.o utils.o
DEPS = $(SRCS:.cpp=.d)       # 依賴文件列表, 如 main.d, utils.d.PHONY: all
all: $(TARGET)$(TARGET): $(OBJS)$(CC) $(LDFLAGS) $(OBJS) -o $(TARGET)

解釋:

  • %.o: %.cpp 是一個模式規則% 是通配符。它告訴 make 如何將任意 .cpp 文件編譯成 .o 文件。

六、Makefile常用的幾個自動變量

👍 make 提供了幾個非常有用的自動變量 (Automatic Variables),它們可以在規則的命令中使用,讓 Makefile 更加簡潔和通用。


核心自動變量

這些變量在每個規則的命令執行時,會根據當前規則的上下文自動展開。

  1. $@ - 目標文件名 (Target)

    • 含義:代表當前規則的目標 (Target)

    • 用途:當你有多個規則,且目標名各不相同時,用 $@ 可以避免重復寫目標名。

    • 例子

      # 假設當前規則是: program: main.o utils.o
      # 那么 $@ 就代表 "program"
      program: main.o utils.o$(CC) $^ -o $@  # 等價于 $(CC) main.o utils.o -o program
      
  2. $^ - 所有依賴文件名列表 (All Prerequisites)

    • 含義:代表當前規則中列出的所有依賴項 (Dependencies),用空格分隔。如果有重復的依賴,$^ 會包含重復項。

    • 用途:在鏈接或編譯命令中,一次性引用所有依賴。

    • 例子

      # 規則: program: main.o utils.o
      # 那么 $^ 就代表 "main.o utils.o"
      program: main.o utils.o$(CC) $^ -o $@  # 等價于 $(CC) main.o utils.o -o program
      
  3. $< - 第一個依賴文件名 (First Prerequisite)

    • 含義:代表當前規則中的第一個依賴項

    • 用途:在模式規則中非常有用,比如從 .cpp 編譯 .o 時,$< 就是 .cpp 文件。

    • 例子

      # 模式規則: %.o: %.cpp
      # 當 make 處理 main.cpp -> main.o 時
      # $< 代表 "main.cpp", $@ 代表 "main.o"
      %.o: %.cpp$(CC) $(CFLAGS) -c $< -o $@  # 等價于 g++ -c main.cpp -o main.o
      

為什么 $^$< 有區別?(重要!)

雖然在很多簡單情況下 $^$< 看起來一樣(比如只有一個依賴),但當依賴有多個或有重復時,它們就不同了。

  • $^ 包含所有依賴,包括重復項。
  • $< 只包含第一個依賴。

例子:

# 假設有一個奇怪的規則(實際很少見)
program: main.o utils.o main.o  # main.o 被列了兩次@echo "Dependencies: $^"     # 輸出: Dependencies: main.o utils.o main.o@echo "First dep: $<"        # 輸出: First dep: main.o

在鏈接命令中,你通常希望傳遞所有目標文件,所以用 $^。而在編譯單個源文件時,你只需要當前的源文件,所以用 $<


使用自動變量優化之前的 Makefile

讓我們用這些自動變量來簡化之前的例子:

# Makefile - 使用自動變量CC = g++
CFLAGS = -Wall -g
TARGET = myapp
SRCS = main.cpp utils.cpp
OBJS = $(SRCS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d).PHONY: all
all: $(TARGET)# 鏈接規則: 使用 $^ (所有 .o 文件) 和 $@ (目標可執行文件)
$(TARGET): $(OBJS)$(CC) $^ -o $@.PHONY: clean
clean:rm -f $(TARGET) $(OBJS) $(DEPS)

優點:

  • 更簡潔$(CC) $^ -o $@$(CC) $(LDFLAGS) $(OBJS) -o $(TARGET) 短且清晰。
  • 更通用:這個鏈接規則可以用于任何由 .o 文件鏈接而成的目標,你不需要修改命令。
  • 更可靠:在模式規則中,$< 確保只傳遞當前正在編譯的源文件。

總結
  • $@ = 目標 (Target) - 你要生成的東西。
  • $^ = 所有依賴 (All Prerequisites) - 生成目標需要的所有輸入文件(鏈接時用)。
  • $< = 第一個依賴 (First Prerequisite) - 通常用于編譯單個源文件時的源文件名。

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

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

相關文章

分布式微服務--Nacos作為配置中心(補)關于bosststrap.yml與@RefreshScope

一、關于bosststrap.yml? bootstrap.yml 和 application.yml 的區別對比項bootstrap.ymlapplication.yml加載時機優先于 application.yml 加載&#xff08;啟動早期&#xff09;程序初始化完成后加載主要用途設置應用的外部配置源、注冊中心信息等設置應用內部配置&#xff0c…

[Qt]QString 與Sqlite3 字符串互動[漢字不亂碼]

環境&#xff1a;Qt C&#xff08;msvc c&#xff09;1.將與數據庫交互的代碼文件編碼轉換為utf-8-bom編碼&#xff0c;&#xff08;可使用notepad 進行轉換&#xff09;2.在代碼文件頭文件中加上下面代碼。//vs2010 版本是 1600 #if defined(_MSC_VER) && (_MSC_VER &…

SpringBoot啟動項目詳解

SpringBoot 的啟動過程是一個整合 Spring 核心容器、自動配置、嵌入式服務器等功能的復雜流程&#xff0c;核心目標是 “簡化配置、快速啟動”。下面從入口類開始&#xff0c;逐步拆解其詳細啟動步驟&#xff1a;一、啟動入口&#xff1a;SpringBootApplication與main方法Sprin…

PCB 控深槽如何破解 5G 基站 120℃高熱魔咒?

5G 基站在高頻通信下的功耗較 4G 基站提升 3-4 倍&#xff0c;射頻模塊、電源單元等核心部件的工作溫度常突破 120℃&#xff0c;遠超設備安全閾值&#xff08;≤85℃&#xff09;&#xff0c;形成制約通信穩定性的 “高熱魔咒”。印制線路板&#xff08;PCB&#xff09;作為熱…

NEXT.js 打包部署到服務器

在網上查了一下&#xff0c;記錄一下1.首先執行打包命令&#xff0c;我這個項目是用的pnpm&#xff0c;可以根據項目需求使用 npm 或者別的pnpm run build2.打包完成后會有一個 .next 的文件夾&#xff0c;需要把下圖的這些文件放到服務器。服務器需要有node環境之后就需要執行…

【AI分析】uv庫自動安裝腳本uv-installer-0.8.3.ps1分析

目錄uv 安裝腳本完整分析報告1. 腳本概述2. 參數解析3. 環境變量控制4. 核心函數詳解a. Install-Binary&#xff08;主控函數&#xff09;b. Get-TargetTriple&#xff08;架構檢測&#xff09;c. Download&#xff08;下載處理&#xff09;d. Invoke-Installer&#xff08;安裝…

etcd 的安裝與使用

介紹 Etcd 是一個 golang 編寫的分布式、高可用的一致性鍵值存儲系統&#xff0c;用于配置共享和服 務發現等。它使用 Raft 一致性算法來保持集群數據的一致性&#xff0c;且客戶端通過長連接 watch 功能&#xff0c;能夠及時收到數據變化通知&#xff0c;相較于 Zookeeper 框…

conda : 無法將“conda”項識別為 cmdlet、函數、腳本文件或可運行程序的名稱。請檢查名稱的拼寫,如果包括路徑,請確保路徑正

詳細問題 PS C:\Users\wh109> conda init powershell conda : 無法將“conda”項識別為 cmdlet、函數、腳本文件或可運行程序的名稱。請檢查名稱的拼寫&#xff0c;如果包括路徑&#xff0c;請確保路徑正 確&#xff0c;然后再試一次。 所在位置 行:1 字符: 1conda init pow…

HQChart實戰教程58:K線主圖仿TradingView實現

本文將詳細介紹如何使用HQChart實現類似TradingView風格的K線主圖,包含完整的代碼實現和詳細注釋,適合金融圖表開發者和量化交易愛好者閱讀。 一、TradingView風格特點分析 在開始實現前,我們先分析TradingView的K線主圖核心特點: 簡潔現代的UI設計:深色背景、清晰的網格…

GitPython08-源碼解讀

GitPython08-源碼解讀 1-核心知識 1&#xff09;gitPython核心代碼很多都是對git命令返回的結果進行解析&#xff0c;在此補充git命令的返回內容2&#xff09;git ls-tree -> 查看某個提交或分支所對應的目錄樹3&#xff09;源碼中Tree對應的業務邏輯 -> 獲取git ls-tre…

中科院開源HYPIR圖像復原大模型:1.7秒,老照片變8K畫質

目錄 前言 一、告別“龜速”藝術家&#xff0c;擁抱“閃電”打印機 二、不止是高清&#xff1a;它看得懂文字&#xff0c;更能理解你的心意 2.1 首先&#xff0c;它是位“文字保衛者” 2.2 其次&#xff0c;它還是個“細節創造家” 2.3 最后&#xff0c;它是一個能“聽懂…

設計Mock華為昇騰GPU的MindSpore和CANN的庫的流程與實現

在沒有華為昇騰GPU可用的情況下用C庫寫一個Mock MindSpore和CANN的庫&#xff0c;調用多核CPU和內存的資源來模擬GPU的算力&#xff0c;調試MindSpore和CANN的C代碼&#xff0c;做這個庫的基本流程步驟和詳細設計是什么&#xff1f; 要創建一個Mock庫來模擬華為昇騰GPU&#xf…

【AI問答記錄】grafana接收query請求中未攜帶step參數,后端基于intervalMs和maxDataPoints等參數計算step的邏輯

讓我詳細分析Grafana源碼中計算step的完整邏輯&#xff0c;它確實比簡單的intervalMs/1000復雜得多。 完整的Step計算流程 1. 入口點&#xff1a;[models.Parse](file://F:\JavaProject\grafana-release-11.2.0\pkg\promlib\models\query.go#L190-L274)函數 在pkg/promlib/mode…

再談亞馬遜云科技(AWS)上海AI研究院7月22日關閉事件

【科技明說 &#xff5c; 科技熱點關注】亞馬遜云科技&#xff08;AWS&#xff09;上海AI研究院已于2025年7月22日正式解散&#xff0c;這是亞馬遜在全球范圍內的最后一個海外研究中心的關閉。這個消息是否是真的&#xff0c;目前得到的印證來自其研發中心的首席科學家王敏捷在…

Python中的決策樹機器學習模型簡要介紹和代碼示例(基于sklearn)

一、決策樹定義 決策樹是一種監督學習算法&#xff0c;可用于**分類&#xff08;Classification&#xff09;和回歸&#xff08;Regression&#xff09;**任務。 它的結構類似樹狀結構&#xff1a; 內部節點&#xff1a;特征條件&#xff08;如X > 2&#xff09;葉子節點&am…

Redis集群分布式(Redis Cluster)底層實現原理詳細介紹

文章目錄一、Redis集群概念二、集群節點1. 節點如何啟動2. 節點的集群數據結構2.1 clusterNode結構2.2 clusterLink結構2.3 clusterState結構3. 節點如何加入集群三、數據分片機制1. 記錄節點的槽指派信息2. 傳播節點的槽指派信息3. 記錄集群所有槽的指派信息4. 節點的槽指派命…

【走遍美國精講筆記】第 1 課:林登大街 46 號

ACT 1-1 “我可以給您和您的小男孩拍張照嗎&#xff1f;” 【故事梗概】 自由攝影藝術家 Richard Stewart&#xff0c;正在為編出自己的影集《走遍美國》到處拍照。今天他在由紐約市曼哈頓區到斯塔滕島的渡船上工 作&#xff0c;回程中遇到了來自加州的一位黑人婦女 Martha Van…

Java中Lambda 表達式的解釋

從 Java 8 開始&#xff0c;Lambda 表達式成為 Java 的一等公民。它不僅讓代碼更簡潔&#xff0c;還為函數式編程打開了大門。如果你還沒真正理解或使用過 Lambda&#xff0c;這篇文章就是為你寫的。一、什么是 Lambda 表達式&#xff1f;Lambda 表達式是 Java 中的一種匿名函數…

Spring AI調用Embedding模型返回HTTP 400:Invalid HTTP request received分析處理

調用Embedding模型失敗 Spring AI項目使用的Embedding模型是公司平臺部署的&#xff0c;請求模型服務的時候報錯&#xff0c;返回了HTTP 400 - Invalid HTTP request received錯誤。然后換成云廠商在線Embedding模型地址&#xff0c;正常調通。我用Apifox直接調用公司的模型服務…

Pytorch-02數據集和數據加載器的基本原理和基本操作

1. 為什么要有數據集類和數據加載器類&#xff1f; 一萬個人會有一萬種獲取并處理原始數據樣本的代碼&#xff0c;這會導致對數據的操作代碼標準不一&#xff0c;并且很難復用。為了解決這個問題&#xff0c;Pytorch提供了兩種最基本的數據相關類&#xff1a; torch.utils.data…