利用Makefile給多文件、多目錄C源碼建立工程


0. 前言

粉絲留言,想知道如何使用Makefile給多個文件和多級目錄建立一個工程,必須安排!

關於Makefile的入門參考文章,可以先看這篇文章:

Makefile入門教程

為了讓大家有個更加直觀的感受,一口君將之前寫的一個小項目,本篇在該項目基礎上進行修改。

該項目詳細設計和代碼,見下文:

從0寫一個《電話號碼管理系統》的C入門項目【適合初學者】

一、文件

好了,開始吧!

我們將該項目的所有功能函數放到以該函數名命名的c文件,同時放到對應名稱的子目錄中。

比如函數allfree(),存放到 allfree/allfree.c中

最終目錄結構如下圖所示:

 peng@ubuntu:/mnt/hgfs/code/phone$ tree .
.
├── allfree
│   ├── allfree.c
│   └── Makefile
├── create
│   ├── create.c
│   └── Makefile
├── delete
│   ├── delete.c
│   └── Makefile
├── display
│   ├── display.c
│   └── Makefile
├── include
│   ├── Makefile
│   └── phone.h
├── init
│   ├── init.c
│   └── Makefile
├── login
│   ├── login.c
│   └── Makefile
├── main
│   ├── main.c
│   └── Makefile
├── Makefile
├── menu
│   ├── Makefile
│   └── menu.c
├── scripts
│   └── Makefile
└── search
    ├── Makefile
    └── search.c

11 directories, 22 files

直接看下編譯結果吧:

peng@ubuntu:/mnt/hgfs/code/phone$ make
make[1]: Entering directory '/mnt/hgfs/code/phone/allfree'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/allfree'
make[1]: Entering directory '/mnt/hgfs/code/phone/create'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/create'
make[1]: Entering directory '/mnt/hgfs/code/phone/delete'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/delete'
make[1]: Entering directory '/mnt/hgfs/code/phone/display'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/display'
make[1]: Entering directory '/mnt/hgfs/code/phone/init'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/init'
make[1]: Entering directory '/mnt/hgfs/code/phone/login'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/login'
make[1]: Entering directory '/mnt/hgfs/code/phone/menu'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/menu'
make[1]: Entering directory '/mnt/hgfs/code/phone/search'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/search'
make[1]: Entering directory '/mnt/hgfs/code/phone/main'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/main'
gcc -Wall -O3 -o phone allfree/*.o create/*.o delete/*.o display/*.o init/*.o login/*.o menu/*.o search/*.o main/*.o -lpthread
phone make done! 

運行結果如下:

二、Makefile常用基礎知識點

[0] 符號'@' '$' '$$' '-' '-n ' 的說明

  1. '@'
    通常makefile會將其執行的命令行在執行前輸出到屏幕上。
    如果將‘@’添加到命令行前,這個命令將不被make回顯出來。
    例如:
@echo  --compiling module----;  // 屏幕輸出  --compiling module----
echo  --compiling module----;  // 沒有@ 屏幕輸出echo  --compiling module----   
  1. ' - '

通常刪除,創建文件如果碰到文件不存在或者已經創建,那么希望忽略掉這個錯誤,繼續執行,就可以在命令前面添加 -,

-rm dir;
-mkdir aaadir;
  1. ' $ '
    美元符號$,主要擴展打開makefile中定義的變量

  2. ' $$ '
    $$ 符號主要擴展打開makefile中定義的shell變量

[1] wildcard

說明:
列出當前目錄下所有符合模式“ PATTERN”格式的文件名,並且以空格分開。“ PATTERN”使用shell可識別的通配符,包括“ ?”(單字符)、“ *”(多字符)等。
示例:

$(wildcard *.c) 

返回值為當前目錄下所有.c 源文件列表。

[2] patsubst

說明:把字串“ x.c.c bar.c”中以.c 結尾的單詞替換成以.o 結尾的字符。
示例:

$(patsubst %.c,%.o,x.c.c bar.c)

函數的返回結果

 x.c.o bar.o

[3] notdir

說明:去除文件名中的路徑信息
示例:

SRC = ( notdir ./src/a.c ) 

去除文件a . c 的路徑信息 , 使用 (notdir ./src/a.c) 去除文件a.c的路徑信息,使用 (notdir./src/a.c)去除文件a.c的路徑信息,使用(SRC)得到的是不帶路徑的文件名稱,即a.c。

[4] 包含頭文件路徑

使用-I+頭文件路徑的方式可以指定編譯器的頭文件的路徑
示例:

INCLUDES = -I./inc
$(CC) -c $(INCLUDES) $(SRC)

[5] addsuffix

函數名稱:加后綴函數—addsuffix。
語法:

$(addsuffix SUFFIX,NAMES…) 

函數功能:為“NAMES…”中的每一個文件名添加后綴“SUFFIX”。參數“NAMES…”
為空格分割的文件名序列,將“SUFFIX”追加到此序列的每一個文件名
的末尾。
返回值:以單空格分割的添加了后綴“SUFFIX”的文件名序列。
函數說明:
示例:

$(addsuffix .c,foo bar) 

返回值為

foo.c bar.c

[6] 包含另外一個文件:include

在Makefile使用include關鍵字可以把別的Makefile包含進來,這很像C語言的#include,被包含的文件會原模原樣的放在當前文件的包含位置。
比如命令

include file.dep

即把file.dep文件在當前Makefile文件中展開,亦即把file.dep文件的內容包含進當前Makefile文件

在 include前面可以有一些空字符,但是絕不能是[Tab]鍵開始。

[7] foreach

foreach函數和別的函數非常的不一樣。因為這個函數是用來做循環用的
語法是:

$(foreach <var>,<list>,<text> )

這個函數的意思是,把參數 中的單詞逐一取出放到參數 所指定的變量中,然后再執行 所包含的表達式。

每一次 會返回一個字符串,循環過程中, 的所返回的每個字符串會以空格分隔,最后當整個循環結束時, 所返回的每個字符串所組成的整個字符串(以空格分隔)將會是foreach函數的返回值。

所以,最好是一個變量名, 可以是一個表達式,而 中一般會使用 這個參數來依次枚舉 中的單詞。

舉例:

names := a b c d
files := $(foreach n,$(names),$(n).o)

上面的例子中,$(name)中的單詞會被挨個取出,並存到變量“n”中,“$(n).o”每次根據“$(n)”計算出一個值,這些值以空格分隔,最后作為foreach函數的返回,所以,$(files)的值是“a.o b.o c.o d.o”。

注意,foreach中的參數是一個臨時的局部變量,foreach函數執行完后,參數的變量將不在作用,其作用域只在foreach函數當中。

[8] call

“ call”函數是唯一一個可以創建定制化參數函數的引用函數。
使用這個函數可以實現對用戶自己定義函數引用。
我們可以將一個變量定義為一個復雜的表達式,用“ call”函數根據不同的參數對它進行展開來獲得不同的結果。

函數語法:

$(call variable,param1,param2,...)

函數功能:
在執行時,將它的參數“ param”依次賦值給臨時變量“ $(1)”、“ $(2)” call 函數對參數的數目沒有限制,也可以沒有參數值,沒有參數值的“ call”沒有任何實際存在的意義。
執行時變量“ variable”被展開為在函數上下文有效的臨時變量,變量定義中的“ $(1)”作為第一個參數,並將函數參數值中的第一個參數賦值給它;
變量中的“ $(2)”一樣被賦值為函數的第二個參數值;
依此類推(變量$(0)代表變量“ variable”本身)。
之后對變量“ variable” 表達式的計算值。

返回值:
參數值“ param”依次替換“ $(1)”、“ $(2)”…… 之后變量“ variable”定義的表達式的計算值。

函數說明:

  1. 函數中“ variable”是一個變量名,而不是變量引用。因此,通常“ call”函數中的“ variable”中不包含“ $”(當然,除非此變量名是一個計算的變量名)。
  2. 當變量“ variable”是一個 make 內嵌的函數名時(如“ if”、“ foreach”、“ strip”等),對“ param”參數的使用需要注意,因為不合適或者不正確的參數將會導致函數的返回值難以預料。
  3. 函數中多個“ param”之間使用逗號分割。
  4. 變量“ variable”在定義時不能定義為直接展開式!只能定義為遞歸展開式。

函數示例:

reverse = $(2)$(1)
foo = $(call reverse,a,b)
all:
	@echo "foo=$(foo)"

執行結果:

foo=ba

即a替代了$(1),b替代了$(2)

三、編譯詳細說明

我們在根目錄下執行make命令后,詳細步驟如下:

  1. include scripts/Makefile :將文件替換到當前位置,
  2. 使用默認的目標all,該目標依賴於$(Target)
    $(Target) 在scripts/Makefile中定義了,即phone
  3. $(Target)依賴於mm
  4. mm這個目標會執行
@ $(foreach n,$(Modules),$(call modules_make,$(n)))

Modules是所有的目錄名字集合,
foreach 會遍歷字符串$(Modules)中每個詞語,
每個詞語會賦值給n,
同時執行語句:

call modules_make,$(n)
  1. modules_make 被$(MAKE) -C $(1)所替代,

$(MAKE) 有默認的名字make
-C:進入子目錄執行make
$(1) :是步驟4中$(n),即每一個目錄名字

最終步驟4的語句就是進入到每一個目錄下,執行每一個目錄下的Makefile

  1. 進入某一個子目錄下,執行Makefile
    默認目標是all,依賴Objs
Objs := $(patsubst %.c,%.o,$(Source))

patsubst 把字串$ource中以.c 結尾的單詞替換成以.o 結尾的字符

Source := $(wildcard ./*.c)

wildcard 會列舉出當前目錄下所有的.c文件

所以第6步最終就是將子目錄下的所有的.c文件,編譯生成對應文件名的.o文件

$(CC) $(CFLAGS) -o $(Target) $(AllObjs) $(Libs)

這幾個變量都在文件scripts/Makefile中定義
$(CC) :替換成gcc,制定編譯器
$(CFLAGS) :替換成-Wall -O3,即編譯時的優化等級
-o $(Target):生成可執行程序phone
$(AllObjs)

AllObjs := $(addsuffix /*.o,$(Modules))

addsuffix 會將 /*.o追加到$(Modules)中所有的詞語后面,也就是我們之前在子目錄下編譯生成的所有的.o文件
$(Libs) :替換為-lpthread,即所需要的動態庫

大家可以根據這個步驟,來分析一下執行make clean時,執行步驟

完整的實例程序公眾號后台回復:電話號碼管理

《電話號碼管理-makefile版.rar》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM