1. 程式人生 > >Makefile新手向教程:跟著+c同學step by step寫makefile

Makefile新手向教程:跟著+c同學step by step寫makefile

前言

最近在寫底層C程式碼需要用到makefile來簡化編譯流程並優化檔案目錄結構,一直沒找到很好的makefile教程(一個通俗易懂的漸進式的教程),通過+c同學終於是找到了他在之前在學校實訓的時候寫的一篇文章,由於網站只能通過校內網查閱,在此決定分享一下,並留作個人往後學習查閱使用。

正文

原文名字:Makefile的使用
原文作者:DaddyTrap

Makefile簡介

Makefile是一個檔名為Makefile的文字檔案,用於定義專案的編譯規則,以便於整個專案的編譯。

(建立方法:touch Makefile 編輯方法:gedit Makefile vim Makefile

…)

如果不使用Makefile,可能我們就需要跟之前一樣手打一大串編譯命令來編譯程式碼——大一時便是深有體會。g++ main.cpp support1.cpp support2.cpp support3.cpp ...這樣的命令每次都要打一遍,如果是在平時的題目倒還好,如果遇到有數十個cpp和hpp的專案那就不好玩了,而且這樣編譯,有些依賴關係不清楚也是一個麻煩。

Makefile中就可以定義好各個檔案的依賴關係,在之後再需要編譯時,只需要執行make命令就可以自動編譯了。

在一次make之後,一般 會生成很多 目標檔案(*.o) 和一個可執行檔案,當這些檔案和原始碼都沒有被修改時,再次執行make

會提示make: 'bin/your_program' is up to date.,而當你只修改了一個原始碼檔案再執行make時,它也不會重複編譯已經最新的檔案,而只編譯依賴了你的原始碼的檔案,這對提高編譯效率是非常重要的。

編譯過程

關於編譯過程,本文就不再贅述,僅提供一篇部落格(←這是超連結)吧。

Makefile初級教程

規則 的基本語法:

target ... : prerequisites ...  
    command
    ...
    ...
  • target 是下面的命令的 目標 ,即下面命令是為了target而生的。這個 目標 可以是*.o檔案,也可以是可執行檔案
  • prerequisites 則是生成該目標所 依賴 的檔案,如果找不到依賴的檔案,下面的命令就不會執行且會中斷 make
  • command 就是生成目標檔案的命令,一般就是編譯命令了,如g++ main.cpp等等(注意:命令前面必須有 Tab (‘\t’) 真正 的Tab,這樣make才會認為它是指令)

直接看一個例子會更加直觀。

假設有下列三個檔案和下述的目錄結構

// main.cpp
#include <iostream>
#include <string>
#include "support1.hpp"

int main() {  
  std::string s = "Testing";
  support1::PrintItself(s);
  return 0;
}
// support1.cpp
#include "support1.hpp"
void support1::PrintItself(std::string s) {  
  std::cout << s << std::endl;
}
// support.hpp
#include <iostream>
namespace support1 {  
  void PrintItself(std::string);
}
# 目錄結構
user@computer:~/learnmake$ tree  
.
├── main.cpp
├── Makefile
├── support1.cpp
└── support1.hpp

main.cpp中include了support1,我們的 目的 是編譯出一個可執行檔案main

毫無疑問可以使用g++ main.cpp support1.cpp -o main這樣的命令,於是就寫成了下面的Makefile

# Makefile version 1
main: main.cpp support1.cpp support1.hpp  
  g++ main.cpp support1.cpp -o main
  • main就是上文說到的target
  • 生成它依賴於所有的程式碼檔案(main.cpp support1.cpp support1.hpp)
  • 生成它的命令是g++ main.cpp support1.cpp -o main

這個Makefile簡單易懂,但是不行,這不程式設計師。這樣的關係還是不太清晰。我們一般讓各個cpp檔案生成.o檔案,再將它們連結起來。也就是需要下面的命令

g++ support1.cpp -c             # 生成support1.o  
g++ main.cpp -c                 # 生成main.o  
g++ main.o support1.o -o main   # 連結生成可執行檔案main  

那就按這個改吧

# Makefile version 2
main: main.o support1.o  
    g++ main.o support1.o -o main

support1.o: support1.hpp support1.cpp  
    g++ -c support1.cpp

main.o: main.cpp  
    g++ -c main.cpp

這裡就可以再說說 依賴關係 怎麼體現了。按順序來讀:

  1. 需要得到目標main,先看看它所依賴的檔案在不在——並沒有
  2. 於是先生成所依賴的檔案main.o和support1.o
  3. main.o依賴main.cpp,在目錄中尋找,有則執行命令,無則報錯
  4. support1.o同上
  5. 依賴檔案已經都存在了,生成目標out
  6. 結束

這樣,一個簡單的Makefile就完成了,然而它還有很多可以改進的地方

先看下面這一個Makefile

# Makefile version 3
CC := g++  
FLAGS := -std=c++11 -w

main: support1.o main.o  
    $(CC) $(FLAGS) main.o support1.o -o [email protected]

support1.o: support1.hpp support1.cpp  
    $(CC) $(FLAGS) -c support1.cpp

main.o: main.cpp  
    $(CC) $(FLAGS) -c main.cpp

clean:  
    @rm -f *.o
    @rm -f *.gch
    @rm -f main

突然出現的這些語法,下面我們逐個說說

  • 變數(如CC := g++$(CC)
  • 在Makefile中可以使用"<name> := <content>"宣告變數,並在此後使用$(<name>)可以使用,類似C/C++中的巨集,它是字串的直接替換。如$(CC) $(FLAGS) -c support1.cpp會變成g++ -std=c++11 -w -c support1.cpp
  • 字首是$符號,後面帶個括號()的都是變數,如[email protected]也是變數,稱為 自動化變數,它是目標檔案的名字。如目標為main的這一段的命令會變成g++ -std=c++ -w main.o support1.o -o main
  • 偽目標(沒有依賴檔案的目標clean
  • clean一段中,target後面沒有依賴檔案,這稱為 偽目標,作用是寫了之後就可以使用make clean來執行它的命令集了
  • 這裡的命令集可以使用許多shell命令,但似乎並不是所有都能用
  • 命令前面的@表示 不顯示這條命令 ,和Windows下的.bat類似(下面分別是不使用@和使用@的對比)
Makefile中加入
foo:  
  echo "Testing"
# 同樣地,這是偽目標,可以使用make foo來執行其命令集

user@computer:~/learnmake$ make foo  
echo "Testing"  
Testing

Makefile中改為  
foo:  
  @echo "Testing"

user@computer:~/learnmake$ make foo  
Testing

複雜一點的情況

有時候一個專案的目錄並沒有如此簡單,例如:

.
├── bin
├── build
├── include
├── lib
   ├── mysql
   └── mysql++
└── src
  • bin是編譯生成的可執行檔案
  • build是編譯的中間檔案如*.o檔案
  • include是各種.h或.hpp檔案
  • lib是一些必要的庫檔案
  • src是.cpp檔案

這個時候,事情並不簡單。因為現在的程式碼“身首異處”,所以要把它們的路徑告訴編譯器才行,我們使用 相對路徑 來達到效果。

.表示當前目錄,..表示上一級目錄,用類似這樣的相對路徑來找到需要的檔案,當然./經常可以省略……(於是本文並沒有者兩個的使用)

以下面的目錄結構為目標(假設原本不存在bin目錄和build目錄):

.
├── bin
│   └── main
├── build
│   ├── main.o
│   └── support1.o
├── include
│   └── support1.hpp
├── Makefile
└── src
    ├── main.cpp
    └── support1.cpp

因為最後的目標 main 在 bin 目錄中,所以 target 也應該是 bin/main,而其依賴的兩個檔案也要寫清楚目錄了 build/main.o build/support1.o。

而在編譯前,應該確認 bin 和 build 都是存在的目錄,因此需要在編譯前多一條命令 mkdir -p bin。因為要指明檔案目錄,於是編譯命令變成g++ -std=c++11 -w build/main.o build/support1.o -o bin/main

那麼編譯生成support1.o的時候命令就是g++ -std=c++11 -w src/support1.cpp -c -o build/support1.o

:-)騙你的

由於在support1.cpp中我們寫了#include "support1.hpp",按以前的經驗,這樣寫是隻能找到同一個資料夾下的檔案的,所以需要加一個編譯器的引數-I./include,讓它去其他地方找標頭檔案。這樣編譯命令就完成了,生成.o檔案的也類似。那麼Makefile就可以寫出來了

CC := g++  
FLAGS := -std=c++11 -w  
bin/main: build/support1.o build/main.o  
    @mkdir -p bin
    $(CC) $(FLAGS) -I./include build/support1.o build/main.o -o [email protected]

build/support1.o: src/support1.cpp  
    @mkdir -p build
    $(CC) $(FLAGS) -I./include -c -o [email protected] src/support1.cpp

build/main.o: src/main.cpp  
    @mkdir -p build
    $(CC) $(FLAGS) -I./include -c -o [email protected] src/main.cpp

clean:  
    @rm -rf build
    @rm -rf bin

(可能有朋友注意到這裡的support1.hpp去掉了,因為編譯的時候就會找support1.hpp,找不到就會編譯錯誤,所以它是否寫在依賴規則裡並不影響結果,也就可有可無了 訂正:在這裡只是為了達到使用下文萬用字元%就包含兩個cpp的效果,實際上寫這個也是必要的,例如:support1.hpp 被修改後,再次使用make,如果不寫,那麼make就不會重新編譯了)

這樣就好了嗎?不,不可以。將來要是檔案結構有了偏差,寫這個Makefile的人是要負責的。如果目錄不同了,那麼改各個路徑要每個都改,而最好的做法應該是隻改一處就影響全域性。所以應該下面這樣會更好。

CC := g++  
FLAGS := -std=c++11 -w  
INC_DIR := include  
SRC_DIR := src  
BUILD_DIR := build  
BIN_DIR := bin  
INCLUDE := -I./$(INC_DIR)  
$(BIN_DIR)/main: $(BUILD_DIR)/support1.o $(BUILD_DIR)/main.o
    @mkdir -p $(BIN_DIR)
    $(CC) $(FLAGS) $(INCLUDE) $(BUILD_DIR)/support1.o $(BUILD_DIR)/main.o -o [email protected]

$(BUILD_DIR)/support1.o: $(SRC_DIR)/support1.cpp
    @mkdir -p $(BUILD_DIR)
    $(CC) $(FLAGS) $(INCLUDE) -c -o [email protected] $(SRC_DIR)/support1.cpp

$(BUILD_DIR)/main.o: $(SRC_DIR)/main.cpp
    @mkdir -p $(BUILD_DIR)
    $(CC) $(FLAGS) $(INCLUDE) -c -o [email protected] $(SRC_DIR)/main.cpp

clean:  
    @rm -rf $(BUILD_DIR)
    @rm -rf $(BIN_DIR)

對於不同的情況,只要一個一個目標、一個一個依賴來寫就好了

下面用的是萬用字元,並不適用於所有情況,還請各位謹慎使用

然而這個Makefile還可以更加簡短並更加完善,如下面程式碼(出現的新東西,後面細說)

CC := g++  
FLAGS := -std=c++11 -w  
INC_DIR := include  
SRC_DIR := src  
BUILD_DIR := build  
BIN_DIR := bin  
INCLUDE := -I./$(INC_DIR)

$(BIN_DIR)/main: $(BUILD_DIR)/support1.o $(BUILD_DIR)/main.o
    @mkdir -p $(BIN_DIR)
    $(CC) $(FLAGS) $(INCLUDE) $^ -o [email protected]

$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp
    @mkdir -p $(BUILD_DIR)
    $(CC) $(FLAGS) $(INCLUDE) -c -o [email protected] $<

clean:  
    @rm -rf $(BUILD_DIR)
    @rm -rf $(BIN_DIR)
  • 兩個自動化變數
  • $^依賴檔案的集合,用空格分隔
  • $<第一個依賴檔案
  • 其實在%.o部分,也可以換成$^
  • 還有上文已經提過的 [email protected]目標檔案
  • 萬用字元%
  • 作用是就是*的作用了……通配……

這裡的好處就是當src目錄下再多cpp檔案時,生成%.o檔案的這一部分不用更改,生成可執行檔案那裡只要加一個依賴就好了

實際上還有方法讓生成可執行檔案的規則也自動,但又會聊到更加深的內容了

或許這篇新手向的教程並不讓你盡興,因此如果你想了解更多的Makefile知識,可以參考下面兩篇文章

相關推薦

Makefile新手教程跟著+c同學step by stepmakefile

前言 最近在寫底層C程式碼需要用到makefile來簡化編譯流程並優化檔案目錄結構,一直沒找到很好的makefile教程(一個通俗易懂的漸進式的教程),通過+c同學終於是找到了他在之前在學校實訓的時候寫的一篇文章,由於網站只能通過校內網查閱,在此決定分享一下,

web前端新手入門教程Web 框架的架構模式探討

在寫乾貨之前,我想先探(qiang)討(diao)兩個問題,模式的侷限性?模式有什麼用?   最近看到一篇文章對我啟發很大,許來西在知乎的回答《哲學和科學有什麼關聯?》,全篇較長,這裡摘錄我要引出的一點: 科學作為一種經驗主義的認識論,有著經驗主義的巨大缺陷:它永遠不能產生絕對

教程】PDF控制元件Spire.PDF 教程C#中加密和解密PDF檔案

請注意,該教程只適用Spire.PDF 3.9.421及其以上版本。該教程顯示瞭如何使用C#通過以下兩個部分使用密碼來保護和取消保護PDF檔案: 加密PDF解密PDF 加密PDF 有兩種密碼可以用於加密PDF,開啟密碼和修改密碼。 開啟的密碼只設置為開啟密碼。 修改密碼

新手入門教程關於網站建設的主要流程和步驟

對於初涉網際網路的新手來說,網站建設的主要流程是必須瞭解的,這裡就給大家介紹下網站建設的主要步驟! 一、域名註冊 網站建設之前必須要想好域名,域名就如同家的住址,別人必須知道你的地址才能訪問你。需要注意的是,域名註冊應該堅持三個原則:域名名稱儘量和網站性質、功能服務相接近

Step By Step(Lua調用C函數)

extern lua環境 class 數量 lsp 相同 虛擬 c語言代碼 cti 原文: http://www.cnblogs.com/stephen-liu74/archive/2012/07/23/2469902.html Lua可以調用C函數的能力將極大的提高Lu

[轉]RDL(C) Report Design Step by Step 3: Mail Label

設置 ted 運算 lec 標題 report 對數 win 信息管理 本文轉自:http://www.cnblogs.com/waxdoll/archive/2006/09/02/493350.html Crystal Report在報表向導中提供了三種向導類型給用戶

《Microsoft SQL Server 2008 Analysis Services Step by Step》學習筆記八使用帳戶智慧(上)

導讀:本文介紹如何使用賬戶智慧(Account Intelligence) 本文末尾提供兩個專案原始碼:AdventureWorks_BI_Begin5和AdventureWorks_BI_End5,顧名思義,開始和完成。另外,包括資料庫檔案SSAS2008SBS_Da

第一次使用樹莓派3代簡易教程step by step

其實前天買的三代樹莓派就到貨了,但是一直沒時間鼓搗,其實以前沒接觸過這東西,心裡起初還是方方的,後來覺得還是蠻簡單的(畢竟人家都發展了那麼多年了嘛),昨晚點亮了螢幕,現在記錄一下。 準備工作: 樹莓派三代(有wifi這一點簡直太爽了) 5V-2A的電源介面卡一個 clas

Step By Step(Lua呼叫C函式)

Lua可以呼叫C函式的能力將極大的提高Lua的可擴充套件性和可用性。對於有些和作業系統相關的功能,或者是對效率要求較高的模組,我們完全可以通過C函式來實現,之後再通過Lua呼叫指定的C函式。對於那些可被Lua呼叫的C函式而言,其介面必須遵循Lua要求的形式,即typedef

Caffe使用step by stepcaffe框架下的基本操作和分析

caffe雖然已經安裝了快一個月了,但是caffe使用進展比較緩慢,果然如劉老師說的那樣,搭建起來caffe框架環境比較簡單,但是完整的從資料準備->模型訓練->調引數->合理結果需要一個比較長的過程,這個過程中你需要對caffe中很多東西,細節進行深入

《Microsoft SQL Server 2008 Analysis Services Step by Step》學習筆記十八管理部署

導讀:本文介紹Analysis Services的部署方式和部署機制 。 本文將包括以下內容: ■1、使用BIDS部署Anylysis services 資料庫 ■2、建立XMLA指令碼部署Anylysis services 資料庫 ■3、針對Anylysis services 資料庫伺服器上執行部署

Step by Step WebMatrix網站開發之一Webmatrix安裝

      WebMatrix是微軟提供的一個完全免費的Web開發工具,工具內已整合web伺服器、資料庫和程式架構。筆者最感興趣的是新的Razor,一個ASP.NET新的檢視引擎。該引擎很好的將伺服器程式碼和HTML程式碼融合在一起,使程式碼非常容易閱讀和理解,而且大大減少了

新手】TensorFlow 安裝教程RK3399上運行谷歌人工智能

on() 總結 apt 朋友 alt 小型 start light 做了 從AlphaGo大勝柯潔後,谷歌的人工智能備受關註。人工智能好像離我們好遠,深度學習算法貌似非常復雜。但其實看看你的手機上的語音助手,相機上的人臉識別,今日頭條上幫你自動篩選出來的新聞,還有各大音樂軟

新手c++入門教程1—OI

C++入門教程1 1.C++簡介: C++是C語言的繼承,它既可以進行C語言的過程化程式設計,又可以進行以抽象資料型別為特點的基於物件的程式設計,還可以進行以繼承和多型為特點的面向物件的程式設計。C++擅長面向物件程式設計的同時,還可以進行基於過程的程式設計,因而C++就適

Telerik UI for ASP.NET AJAX教程C#中的函式程式設計

【下載Telerik UI for ASP.NET AJAX最新版本】 在面向物件程式設計(OOP)中,我們習慣於使用物件集合或簡單資料型別。我們經常使用LINQ對這些集合進行排序和過濾,作為業務邏輯行為或資料轉換的一部分。雖然這些是我們經常執行的有用任務,但很容易忘記C#中的函式可以被視為資料

資訊學奧賽系列教程C++邏輯運算子

C++中一共有三個邏輯運算子: 1、邏輯與 && 運算子前後兩個條件都為true才為true  2、邏輯或 ||  運算子前後只要有一個條件為true就為true 3、邏輯非 ! 運算子後的表示式取反,非true為false,非false為true

資訊學奧賽系列教程C++語言比較運算子

比較運算子:       比較運算子,主要用於比較變數或者表示式的大小,C++語言中,一共有6個比較運算子:      1、大於>            2、

新手Vue 2.0 的建議學習順序

注:2.0 已經有中文文件 。如果對自己英文有信心,也可以直接閱讀英文文件。 此指南僅供參考,請根據自身實際情況靈活調整。 歡迎轉載,請註明出處。 起步 1. 紮實的 JavaScript / HTML / CSS 基本功。這是前置條件。 2. 通讀官方教程 (guid

Axure教程 原型設計工具Axure RP新手入門教程(二)流程圖

上一篇文章與大家分享了Axure RP的基礎知識,本篇文章將與大家分享Axure RP中的流程圖功能。 流程圖 Axure RP中的流程圖 您可以使用Axure RP建立流程圖,使用者流程,業務流程模型和其他流程圖。可以自由新增形狀,影象和快照小部件以用作流元素,然後使用“con

新手學Linux在VMware14中安裝CentOS7詳細教程

VMware Workstation14安裝CentOS7.0 詳情教程 1.準備工作 a)下載VMware workstation14 b)下載CentOS7 c)下載SSH Secure Shell Client 2.虛擬機器配置 a)開啟虛擬機器軟體“VMware Wo