【LINUX網路編程】Makefile檔案,網路編程makefile

來源:互聯網
上載者:User

【LINUX網路編程】Makefile檔案,網路編程makefile

《Linux網路編程》(第二版) 第2章的一些讀書筆記 ↓

 

Makefile:在一個含有較多檔案的工程中,定義一系列規則來指定編譯檔案的順序,可用於管理工程。

Makefile指定了工程中的哪些源檔案需要編譯以及如何編譯、需要建立那些庫檔案以及如何建立這些庫檔案、如何最後產生我們想要的可執行檔。為工程編寫Makefile 的好處是能夠使用一行命令來完成“自動化編譯”,編譯整個工程你所要做的唯一的一件事就是在shell 提示符下輸入make命令,整個工程就完全自動編譯。

 

首先來看一下Linux下的GCC(GNU Compiler Collection):這是一個工具集,包含gcc(跟大寫的不一樣), g++等編譯器和ar, nm等工具集。

GCC編譯器對程式的編譯有4個階段:先行編譯 -> 編譯和最佳化 -> 彙編 -> 連結,下面以c代碼作例子:

原始碼(*.c) 【先行編譯 -E】 預先處理後的代碼(*.i) 【編譯和最佳化 -S】 彙編代碼(*.s) 【彙編 -c】 目標檔案(*.o) 【連結】 可執行檔

先行編譯過程是將程式中引用的標頭檔包含進原始碼中,並對一些宏進行替換

編譯和最佳化通常是翻譯成組合語言,彙編與機器作業碼之間有一對一的關係

目標檔案是指經過編譯器的編譯和彙編產生的CPU可識別的二進位代碼,但是其中的一些函數過程沒有相關的指示和說明,所以一般不能執行

目標檔案需要用某種方式組合起來才可以運行,這就是連結

 

一些命令選項(假設檔案名稱為hello.c):

gcc hello.c  /* 產生可執行檔,名字為預設的 a.out。gcc後面的不只可以是*.c檔案,同樣也可以是*.o或其他,以下的選項也是如此,只要被處理的檔案的階段在組建檔案的階段之前即可 */

gcc -o hello hello.c  /* 產生可執行檔,名字接在-o後面,即hello */

gcc -E hello.c  /* 經過先行編譯的過程,預設名字格式,產生了hello.i */

gcc -S hello.c  /* 經過了先行編譯和編譯最佳化的過程,名字為預設的 hello.s */

gcc -c hello.c  /* (常用) 經過了先行編譯和編譯最佳化以及彙編的過程,產生了目標檔案,名字為預設的 hello.o */

 

 

 

其他選項:

gcc -D宏名 , gcc -DOS_LINUX 則相當於在先行編譯的時候添加了 #define OS_LINUX,即當出現 #ifdef OS_LINUX 的時候,就會滿足條件

gcc -Idir (大寫i),將標頭檔的搜尋路徑擴大,包含dir目錄 (後面會用到)

gcc -Ldir,將連結時使用的連結庫搜尋路徑擴大,包含dir目錄,gcc優先使用共用程式庫

gcc -static,僅選用靜態程式庫進行連結

gcc -On,n為數字,最佳化程式執行速度和佔用空間(同時會加長編譯速度),常用的是2,gcc -O2

 

瞭解了一些基本選項之後,給出一個小項目的例子,分別用手動編譯和Makefile“自動化編譯”的方式來說明:

目錄結構:

 

project/    main.c    add/        add_int.c        add_float.c    sub/        sub_int.c        sub_float.c

 

main.c:可以注意到add.h和sub.h並沒有跟main.c同一級目錄,這時就要用到 gcc -I目錄 的選項了,擴大標頭檔搜尋路徑,才找到真正的*.h。至於沒有定義的add_int等函數,需要在連結的時候同時包含有具體定義的.o檔案

/* main.c */#include <stdio.h>#include "add.h"#include "sub.h"int main(void){int a = 10, b = 12;float x = 1.23456, y = 9.87654321;printf("int a+b IS:%d\n",add_int(a,b));printf("int a-b IS:%d\n",sub_int(a,b));printf("float a+b IS:%f\n",add_float(x,y));printf("float a-b IS:%f\n",sub_float(x,y));return 0;}

add/add.h

/* add.h */#ifndef __ADD_H__#define __ADD_H__extern int add_int(int a, int b);extern float add_float(float a, float b);#endif

add/add_int.c

/* add_int.c */int add_int(int a, int b){return a+b;}

add/add_float.c

/* add_float.c */float add_float(float a, float b){return a+b;}

sub目錄略。

根據書上的代碼,將c檔案轉成目標檔案,然後連結上。其中,如果在編譯main.c的時候,由於標頭檔找不到,所以必須添加上 -Iadd 和 -Isub 才可以找到,書上的例子有誤。

可以看到,編譯一個小的項目就需要如此多的步驟:

gcc -o add/add_int.o -c add/add_int.c gcc -o add/add_float.o -c add/add_float.c gcc -o sub/sub_float.o -c sub/sub_float.c gcc -o sub/sub_int.o -c sub/sub_int.c gcc -o main.o -c main.c -Iadd -Isubgcc -o cacu add/add_int.o add/add_float.o sub/sub_int.o sub/sub_float.o main.o ./cacu

雖然,可以通過gcc的預設規則,使用如下命令也可以產生可執行檔:

gcc -o cacu1 add/add_int.c add/add_float.c sub/sub_int.c sub/sub_float.c main.c -Iadd -Isub

但是當頻繁修改源檔案或者當項目中的檔案比較多,關係比較複雜的時候,用gcc直接編譯就會變得非常困難。

 

於是我們應該採用Makefile檔案的編寫,通過make命令將多個檔案編譯為可執行檔。

make通過解析Makefile檔案中的規則,可以自動執行相應的指令碼,以下就是一個簡單的Makefile檔案:

# 第一行item的":"左邊為make命令在正確操作之後預設產生的檔案,故產生cacu2,依賴於":"右邊的檔案。# 每一個item都是一個規則# 在從左往右掃描的過程中,如果不存在某個檔案,則跳到產生它的規則# 注意底下的一行,它是以tab鍵開頭的,不能是空格,表示滿足所有依賴的情況下,執行那幾行指令cacu2:add_int.o add_float.o sub_int.o sub_float.o main.ogcc -o cacu2 add/add_int.o add/add_float.o sub/sub_int.o sub/sub_float.o main.oadd_int.o:add/add_int.c add/add.hgcc -o add/add_int.o -c add/add_int.cadd_float.o:add/add_float.c add/add.hgcc -o add/add_float.o -c add/add_float.csub_int.o:sub/sub_int.c sub/sub.hgcc -o sub/sub_int.o -c sub/sub_int.csub_float.o:sub/sub_float.c sub/sub.hgcc -o sub/sub_float.o -c sub/sub_float.cmain.o:main.c add/add.h sub/sub.hgcc -o main.o -c main.c -Iadd -Isub# 清理的規則,可以使用make clean來主動執行clean:rm -f cacu2 add/add_int.o add/add_float.o sub/sub_int.o sub/sub_float.o main.o

,在安裝了make的情況下,通過編寫Makefile後執行make命令,系統會自動執行gcc的一些命令,產生了cacu2這個可執行檔;然後make clean之後,便刪除了本次make產生的內容。

回到Makefile檔案中,事實上預設情況下,make會直接執行第一個規則,即cacu2的規則。系統先檢查依賴,並在成功之後執行下述命令:

$(CC)  -o  $(TARGET)  $(OBJS)  $(CFLAGS)

如何理解?上面的命令其實等價於下面這一條,只是用了變數擴充

gcc -o cacu2 add/add_int.o add/add_float.o sub/sub_int.o sub/sub_float.o main.o -Iadd -Isub -O2

CC = gcc , TARGET = cacu2 , OBJS = add/add_int.o add/add_float.o sub/sub_int.o sub/sub_float.o main.o , CFLAGS = -Iadd -Isub -O2

同理,make clean命令是這樣的:

-$(RM)  $(TARGET)  $(OBJS)

等價於rm -f cacu2 add/add_int.o add/add_float.o sub/sub_int.o sub/sub_float.o main.o,注意到RM變數前面有個 “-” 號,它表示當操作失敗時不報錯,命令繼續執行。

 

總結一下要注意的點:

Makefile中使用變數:

為啥引入變數? 在某個規則中添加一項依賴,既要在依賴項中填寫,又要在命令列中填寫,很不方便。

變數有3種:

  1、預定義變數:Makefile中已經定義的變數,使用者可以直接使用這些變數,不用進行定義。(圖來自此連結) 使用方法與自訂變數一樣,見3

    

  2、自動變數:在編譯語句中,會經常出現目標檔案和依賴檔案,自動變數代表這些目標檔案和依賴檔案。

    

  3、使用者自訂變數:

    定義方法: 變數名 = 值。 eg,  OBJS = add_int.o add_float.o ...   # 一行代表一個變數

    使用方法: $(變數名)。  eg,  $(OBJS)

 

/* 此處重寫一份Makefile */

# 定義一些變數CC = gccCFLAGS = -Iadd -Isub -O2TARGET = cacu3OBJS = add/add_int.o add/add_float.o sub/sub_int.o sub/sub_float.o main.o# $(CC) -o $(TARGET) $(OBJS) $(CFLAGS)$(TARGET):$(OBJS)$(CC) -o $(TARGET) $(OBJS) $(CFLAGS)$(OBJS):%o:%c$(CC) -o $@ -c $< $(CFLAGS)clean:-$(RM) $(TARGET) $(OBJS)

在此處運用了定義變數的方式,簡化了文本的維護。

$(CC) -o $@ -c $< $(CFLAGS)  採用了所謂的“靜態模式”的規則,相當於是多條規則,規則如上面所示。

 

搜尋路徑:

在大的系統中,存在很多目錄,手動用-I的方法添加目錄不方便,所以用到了VPATH變數,它可以自動找到指定檔案的目錄並添加到檔案上。

使用方法:VPATH = path1:path2:...:.

用冒號(:)隔開,記得在最後加上目前的目錄 . 

這樣會出現一個問題:目標檔案會放到當前的目錄下,汙染環境!所以,自訂一個變數,建立以該變數為名字的目錄,將*.o檔案放進去。

/* 此處再重寫一份Makefile */

# 發現一個嚴重的錯誤,因為Makefile檔案行末沒有終結符,所以最好不要盲目在後面放注釋,而應該換行。。# 定義一些變數,並引入VPATHCC = gccCFLAGS = -Iadd -Isub -O2# 將所有的目標檔案放置在這個檔案夾中OBJSDIR = objsVPATH = add:sub:.TARGET = cacu4# OBJS = add/add_int.o add/add_float.o sub/sub_int.o sub/sub_float.o main.oOBJS = add_int.o add_float.o sub_int.o sub_float.o main.o# 定義了VPATH之後,OBJS中的檔案夾名就不用寫了# 先檢查目錄是否存在$(TARGET):$(OBJSDIR) $(OBJS)$(CC) -o $(TARGET) $(OBJSDIR)/*.o $(CFLAGS)$(OBJS):%o:%c$(CC) -o $(OBJSDIR)/$@ -c $< $(CFLAGS)# mkdir -p 建立所有遺失的父目錄$(OBJSDIR):mkdir -p ./$@clean:-$(RM) $(TARGET) $(OBJSDIR)/*.o

 

自動推導規則:使用命令make編譯副檔名為c的C語言檔案的時候,源檔案的編譯規則不用明確給出。按照預設的規則,通過依賴中的.o檔案,找到對應的.c檔案將其編譯成目標檔案用於滿足依賴。

# 使用命令make編譯副檔名為c的C語言檔案的時候,源檔案的編譯規則不用明確給出。(會使用一個預設的編譯規則) -- make的隱含規則CC = gccCFLAGS = -Iadd -Isub -O2VPATH = add:sub:.TARGET = cacu5OBJS = add_int.o add_float.o sub_int.o sub_float.o main.o$(TARGET):$(OBJS)$(CC) -o $(TARGET) $(OBJS) $(CFLAGS)clean:-$(RM) $(TARGET) $(OBJS)

 

遞迴make:

當有多人在多個目錄進行程式開發,並且每個人負責一個模組,而檔案在相對獨立的目錄中,這時由同一個Makefile維護代碼的編譯會十分蹩腳。

 

1、遞迴調用的方式:make命令有遞迴調用的作用,它可以遞迴調用每個子目錄的Makefile。

假設目錄add和sub中都有Makefile,則可以用以下兩種方法(第二種比較好):

add:    cd add && $(MAKE)
add:    $(MAKE) -C add

都表示進入add目錄,然後執行make命令

 

2、總控Makefile:調用$(MAKE) -C即可。如果總控Makefile中的一些變數需要傳遞給下層的Makefile,可以使用export命令。

以下是總控的Makefile代碼實現:

export CC = gccCFLAGS = -Iadd -Isub -O2TARGET = cacu6export OBJSDIR = ${shell pwd}/objs$(TARGET):$(OBJSDIR) main.o$(MAKE) -C add$(MAKE) -C sub$(CC) -o $(TARGET) $(OBJSDIR)/*.omain.o:main.c$(CC) -o $(OBJSDIR)/$@ -c $^ $(CFLAGS)$(OBJSDIR):mkdir -p $(OBJSDIR)clean:-$(RM) $(TARGET) $(OBJSDIR)/*.o

以上代碼與書上有一些不同,注意CC需要export到子Makefile中,否則會預設使用cc而不是gcc

${shell pwd}表示執行shell中的pwd命令,即擷取當前路徑

產生的目標檔案(*.o)全都放進項目目錄的objs目錄下

以下是子目錄Makefile:

add/Makefile:

OBJS = add_int.o add_float.oall:$(OBJS)$(OBJS):%o:%c$(CC) -o $(OBJSDIR)/$@ -c $< -O2clean:-$(RM) $(OBJS)

sub/Makefile:

OBJS = sub_int.o sub_float.oall:$(OBJS)$(OBJS):%o:%c$(CC) -o $(OBJSDIR)/$@ -c $< -O2clean:-$(RM) $(OBJS)

 結果如:

 

3、Makefile中的函數

  • 擷取匹配模式的檔案名稱 wildcard:尋找目前的目錄下所有符合模式PATTERN的檔案名稱,傳回值是以空格分割的(符合模式的)檔案名稱列表。    原型:$(wildcard PATTERN) , 如:$(wildcard *.c)
  • 模式替換函數 patsubst:相當於replace函數,尋找字串text中按照空格分開的詞,將符合模式的字串替換成別的字串。    原型:$(patsubst pattern, replacement, text), 如:$(patsubst %.c, %.o, $(wildcard *.c) )
  • 迴圈函數 foreach:在LIST中取出每個用空格分割的VAR單詞,然後執行TEXT運算式,處理結束後輸出。        原型:$(foreach VAR, LIST, TEXT), 如:$(foreach dir, $(DIRS), $(wildcard $(dir)/*.c) )

以下是根據書上的Makefile檔案改寫的一個用函數的Makefile檔案:

CC = gccCFLAGS = -Iadd -Isub -O2TARGET = cacu7DIR = add sub .FILES = $(foreach dir, $(DIR), $(wildcard $(dir)/*.c) )OBJS = $(patsubst %c, %o, $(FILES))$(TARGET):$(OBJS) main.o$(CC) -o $@ $^ -O2$(OBJS):%o:%c$(CC) -o $@ -c $< $(CFLAGS)clean:-$(RM) $(TARGET) $(OBJS)

 

----- 分割線 -----

 

以上的Makefile例子和整個項目代碼可以參見:TyrusChin - Github 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.