Linux C++ 開發4 - 入門makefile一篇文章就夠了

来源:https://www.cnblogs.com/luoweifu/p/18366803
-Advertisement-
Play Games

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制定的規則,構建最終的目標產物。

file

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++之迭代器》一文中有一個例子是這樣的:

一個公司有多個部門,每個部門有多個人組成,這些人中有開發人員,有測試人員,和與項目相關的其它人員,其結構如下圖片。

file

現在要遍歷這個公司的所有開發人員,遍歷這個公司的所有測試人員。

在這篇文章中,我們用迭代器模式實現了這個需求,類的結構圖是這樣的:

file

詳細代碼參見: 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.hppEnumerator.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】 掃碼二維碼,關註微信公眾號,閱讀更多精彩內容
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Python Lambda 用法大全 一、Lambda表達式基礎 Lambda 的組成分為三部分 Lambda表達式是Python中的一種匿名函數,它可以在一行代碼中定義函數並立即調用它。與普通函數不同的是,Lambda函數通常不需要使用def關鍵字去定義,而是在需要時直接使用lambda關鍵字來創 ...
  • 在PHP 7.4中安裝xdebug,出現"configure: error: rtnetlink.h is required, please make sure it is available by installing the correct package"的問題。 在使用sudo yum in ...
  • 寫在前面 今天繼續講JavaIO流的知識! Java 文件與流操作 File 類 File 類用於表示文件和文件夾的抽象。 構造方法 public File(String pathname): 使用路徑名創建 File 對象。 public File(String parent, String ch ...
  • 類中使用shared_ptr()的問題 當我們先定義一個指針,然後再用這個指針構造兩個智能指針 int main() { int* pt = new int(); std::shared_ptr<int> p1(pt); std::shared_ptr<int> p2(pt); std::cout ...
  • 先序: 學習編程語言要先學個輪廓,剛開始只用學核心的部分,一些細節、不常用的內容先放著,現用現查即可;把常用的東西弄熟練了在慢慢補充。 1、 安裝 Python 解釋器 為什麼需要安裝 Python Python 語言本身是由解釋器執行的,因此你需要在你的電腦上安裝 Python 解釋器。這個解釋 ...
  • 先序: 學習編程語言要先學個輪廓,剛開始只用學核心的部分,一些細節、不常用的內容先放著,現用現查即可;把常用的東西弄熟練了在慢慢補充。 1. Java 概述 Java 是一種面向對象的編程語言,由 Sun Microsystems(現在的 Oracle)在 1995 年推出。Java 程式可以在任何 ...
  • 正文 今天還是把周末的所有事情一起寫。每到周末都沒什麼寫日記的動力。 周五下午出去吃飯,吃一半,斯視頻突然打過來。我當時有點懵,不知道他要乾什麼,但還是接了。第一個畫面就是他擺弄自行車。他跟我說,自行車踏板和鏈條有問題,踩下去的時候轉一半就動不了了。 經過我們二人一通瞎猜,發現是後面的鏈條掉了,但是 ...
  • 1. Rust 簡介 Rust 的歷史 起源:Rust 語言最初由 Mozilla 研究員 Graydon Hoare 於 2006 年開始設計,並於 2009 年首次公開。 開發:Rust 是 Mozilla 實驗室的一個項目,目的是創建一種能夠保證記憶體安全同時又不犧牲性能的系統編程語言。 發佈: ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...