編寫高質量的Makefile


分類: c++/c研究 GNU&LINUX163人閱讀 評論(0)收藏舉報

源地址 :http://acm.hrbeu.edu.cn/forums/index.php?showtopic=1827&st=0&gopid=8924&#entry8924

一、前言

回想自己的第一個Makefile,是這個樣子的

CODE
hello:hello.c
???? gcc hello.c -o hello



后來有所進步,陸續地寫了一些大都是這個樣子的Makefile:

CODE

foobar:foo.o bar.o
???? gcc -o foo.o bar.o
foo.o:foo.c
???? gcc -c foo.c
bar.o:foo.c
???? gcc -c bar.c

.PHONY:clean
clean:
???? rm -rf *.o foobar



看上去還行,用起來也不錯,但是隨著程序規模的擴大,每次添加一個新文件,都要手動修改Makefile,實在是不厭其煩。

后來閱讀了一些開源程序的Makefile源代碼,當然,不是automake生成的那種,有了一些心得,幾番進化,一段時間后,感覺對GNU make算是有了些初步的了解,在此總結一下,也算是溫故而知新了。而且我記性比較差?,放在這里算是記錄一下,免得以后忘記。同時也免得大家再去翻那些繁復的手冊,浪費不必要的時間。?

下文中makefile操作的對象有三個文件: foo.c , bar.c 和bar.h,內容分別如下:

foo.c

CODE

#include "bar.h"

int main(){

???? print("Hello, makefile!");
??????????????
???? return 0;
}




bar.c

CODE

#include <stdio.h>

int print(char * msg){

???? printf("%s/n",msg);

???? return 0;
}




bar.h

CODE
int print(char * msg);




OK,該交代的都交代了,進入正題。


二、我的makefile模板

把上個項目的makefile整理了一下,感覺結構比較清晰,可以作為模板供以后使用。

文件內容大體是這個樣子的:

CODE

CC = gcc
CFLAGS = -Wall -O
INCLUDE = -I/usr/include/mysql???????????????????????????? #其實在這里用不著這幾個選項
LFLAGS = -L/usr/lib/mysql -lmysqlclient -lpthread?? #僅做示意之用

TARGET = foobar

SOUCE_FILES = $(wildcard *.c)
OBJS = $(patsubst %.c,%.o,$(SOUCE_FILES))

%.o : %.c %.h
???? $(CC) -c $(CFLAGS) $< -o $@

%.o : %.c
???? $(CC) -c $(CFLAGS) $< -o $@

$(TARGET): $(OBJS)
???? $(CC) $^ -o $@ $(INCLUDE) $(LFLAGS)

.PHONY:clean
clean:
???? $(RM) $(TARGET) $(OBJS)




解釋:

前幾行都是變量的定義,至于為什么要定義這些變量,理由和編程中使用宏定義是一樣的,那就是改一個就可以使很多地方同時生效,避免了重復的工作。

按照慣例:

CC變量指定了使用的編譯器
CFLAGS變量包含了所需的編譯選項
INCLUDE是尋找頭文件的路徑
LFLAGS是加載外部庫時的指定選項。
TARGET變量代表最終要生成的可執行程序

下面的內容就是關鍵了,我們將利用一些GNU make內置的函數與推導規則來完成我們的目標。

首先的任務是自動獲得當前目錄下所有的源文件,好讓我們新添文件后不必再修改Makefile。
完成這個功能的是這行代碼

SOUCE_FILES = $(wildcard *.c)

wildcard 是GNU make程序預定義的一個函數,作用便是獲取匹配模式文件名,原型為$(wildcard PATTERN)。它的詳細說明可以看這里。簡單來說wildcard函數的參數只有一個,就是函數名之后的文件名模式,這里的模式使用shell可識別 的通配符,包括“?”(單字符)、“*”(多字符)等。現在我們的需求是獲取當前目錄下的所有.c文件,模式自然是*.c。

按照最基本的依賴規則,生成TARGET文件依賴于一系列的.o文件,那么如何獲得這些.o文件的列表呢?答案是使用patsubst模式替換函數函數:

$(patsubst %.c,%.o,$(SOUCE_FILES))

模 式替換函數patsubst函數原型為$(patsubst PATTERN,REPLACEMENT,TEXT),相比wildcard,它要復雜一些,顧名思義,三個參數依次代表了匹配模式,替換規則,替換目標 字符串。在這里,我們需要把所有.c替換成.o,所以寫成上面的樣子就可以了。

現在c源文件列表和obj文件列表都有了,下一步就該為每個源文件編寫規則了。

其實很多源文件的編譯規則都是一樣的,就像最開始那個Makefile中那樣

CODE

foo.o:foo.c
???? gcc -c foo.c
bar.o:foo.c
???? gcc -c bar.c



僅僅是文件名不同而已,因此就給了我們提取模式的某種可能性。我在一個關于winsock的makefile中找到了答案:

CODE
.SUFFIXES: .c .o
.c.o:
???????? $(CC) -c $(CFLAGS) $< -o $@



這個規則利用了GNU make的后綴規則。
在這里,當定義了一個目標是“.c.o”的規則時。它的含義是所有“.o”文件的依賴文件是對應的“.c”文件。因此在這條規則下,foo.c將被自動編譯成foo,bar.c被編譯成bar。
而特殊目標.SUFFIXES這句的作用是: 在默認后綴的基礎上,增加了可以作為后綴的關鍵字符串。
其實.c.o是肯定在默認識別的規則中的,不過為了保險起見,還是顯式地聲明一下比較好。

可以看到,這個規則十分的晦澀,反正我第一眼真是沒看明白。因此,新版本的GNU make已經使用模式規則替代了后綴規則。

同樣的功能,利用模式規則實現如下:

CODE
%.o : %.c
???????? $(CC) -c $(CFLAGS) $< -o $@



這樣看起來便清晰多了。如果考慮到頭文件,完美的寫法應該是這樣的:

CODE
%.o : %.c %.h
???????? $(CC) -c $(CFLAGS) $< -o $@



在上面的規則中,還使用了一些GNU make的自動化變量,他們的含義分別如下:

$@ --- 目標文件
$< --- 第一個依賴文件
$^ --- 所有的依賴文件

更多的自動化變量可以參見這里

最后的規則就是生成可執行文件了,很普通,不再贅述。

為了方便調試,可以在makefile中定義一些偽目標。(偽目標的解釋和意義可以看這里)
一般調試用的makefile中都會有兩個偽目標,一個clean,一個debug

對 于clean,手冊里說:“make存在一個內嵌隱含變量“RM”,它被定義為:“RM = rm –f”。因此在書寫“clean”規則的命令行時可以使用變量“$(RM)”來代替“rm”,這樣可以免出現一些不必要的麻煩!”雖然不知道“必要的麻 煩”是什么,但是小心不為過,照著手冊做比較好。

對于debug,和正常模式不同的就是添加了一些編譯選項,修改CFLAGS的內容就可以了。但目前還沒搞明白怎么動態地在makefile里修改變量的內容。這個問題以后再說。

三、在多文件夾情況使用makefile組織代碼

上一段中給出的makefile,對于一般的小程序已經足矣,但是如果代碼文件越來越多,最后不得不放到幾個文件夾中,這時又該怎么辦?
比如說我們準備把bar.c中的函數整理成了一個函數庫libbar放在主程序文件夾中的子文件夾libbar中,這時該如何利用makefile來組織這些文件?
比較好的辦法是在libbar文件夾中放置一個獨立的子makefile,然后在主makefile里調用它。

libbar/Makefile:

CODE

CC = gcc
CFLAGS = -Wall -O
AR = ar
AFLAGS = -r
INCLUDE = -I/usr/include/mysql
LFLAGS = -L/usr/lib/mysql -lmysqlclient -lpthread

TARGET = libbar.a

SOUCE_FILES = $(wildcard *.c)
OBJS = $(patsubst %.c,%.o,$(SOUCE_FILES))

%.o : %.c %.h
???? $(CC) -c $(CFLAGS) $< -o $@

%.o : %.c
???? $(CC) -c $(CFLAGS) $< -o $@

$(TARGET): $(OBJS)
???? $(AR) $(AFLAGS) $(TARGET) $(OBJS)

.PHONY:clean
clean:
???? $(RM) $(TARGET) $(OBJS)




主Makefile:

CODE

CC = gcc
CFLAGS = -Wall -O
INCLUDE = -I./libbar
LFLAGS = -L./libbar -lbar

SHELL = /bin/bash
SUBDIRS = libbar

TARGET = foobar

SOUCE_FILES = $(wildcard *.c)
OBJS = $(patsubst %.c,%.o,$(SOUCE_FILES))

%.o : %.c %.h
???? $(CC) -c $(CFLAGS) $(INCLUDE) $< -o $@

$(TARGET): $(OBJS) libs
???? $(CC) $(OBJS) -o $@ $(INCLUDE) $(LFLAGS)

libs:
???? @ for subdir in $(SUBDIRS); do /
???????? (cd $$subdir && $(MAKE)); /
???? done

.PHONY:clean
clean:
???? $(RM) $(TARGET) $(OBJS)
???? @ for subdir in $(SUBDIRS); do /
???????? (cd $$subdir && $(MAKE) clean); /
???? done




在主makefile中使用了shell的for語句,循環取出SUBDIRS中的子文件夾名,然后進入子文件夾執行make,然后返回。如果在子makefile中出錯,編譯過程將終止。

四、編譯多個目標

不知你有沒有遇到過這樣的情況,那就是需要從很多的代碼,生成很多的可執行文件。

例如編寫了一堆小工具,而每個工具只有一個源文件,用foo.c生成foo,用bar.c生成bar。

一個一個編譯肯定不現實,這時該怎么做?讓我們用GNU make來解決吧!

仔細閱讀手冊,發現GNU make中的靜態模式,正好可以滿足這個要求。

方便閱讀,直接將手冊中關于靜態模式的解釋粘貼如下:

QUOTE
靜態模式規則是這樣一個規則:規則存在多個目標,并且不同的目標可以根據目標文件的名字來自動構造出依賴文件。靜態模式規則比多目標規則更通用,它不需要多個目標具有相同的依賴。但是靜態模式規則中的依賴文件必須是相類似的而不是完全相同的。

靜態模式規則的基本語法:

TARGETS ...: TARGET-PATTERN: PREREQ-PATTERNS ...

COMMANDS

...

“TAGETS”列出了此規則的一系列目標文件。像普通規則的目標一樣可以包含通配符。

“TAGET -PATTERN”和“PREREQ-PATTERNS”說明了如何為每一個目標文件生成依賴文件。從目標模式(TAGET-PATTERN)的目標名字 中抽取一部分字符串(稱為“莖”)。使用“莖”替代依賴模式(PREREQ-PATTERNS)中的相應部分來產生對應目標的依賴文件。




對應我們的需求,應該是用符合%.c模式的文件,生成文件名為%的可執行文件,同時利用自動化變量,構造規則如下:

CODE
$(TARGET_FILES): % : %.cpp
???? g++ $(CFLAGS) $< -o $@



其中$(TARGET_FILES)為最終的可執行文件名,可以用wildcard配合patsubs函數獲得。

因為$(TARGET_FILES)不止一個,所以直接寫這個命令的結果是只會編譯出一個可執行文件,即第目標文件列表中的一個文件,要想成功編譯出所有的,還需要偽目標的幫忙。

完整的makefile如下:

CODE


CC = gcc
CFLAGS = -Wall -O

SOUCE_FILES=$(wildcard *.c)
TARGET_FILES=$(patsubst %.c,%,$(SOUCE_FILES))

.PHONY:all

all:$(TARGET_FILES)

$(TARGET_FILES): % : %.c
???? g++ $(CFLAGS) $< -o $@

clean:
???? $(RM) $(TARGET_FILES)




這里介紹兩種變量的高級使用方法,第一種是變量值的替換。

我們可以替換變量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,其意思是,把變量“var”中所有以“a”字串“結尾”的“a”替換成“b”字串。這里的“結尾”意思是“空格”或是“結束符”。

還是看一個示例吧:

??? foo := a.o b.o c.o
??? bar := $(foo:.o=.c)

這個示例中,我們先定義了一個“$(foo)”變量,而第二行的意思是把“$(foo)”中所有以“.o”字串“結尾”全部替換成“.c”,所以我們的“$(bar)”的值就是“a.c b.c c.c”。

另外一種變量替換的技術是以“靜態模式”(參見前面章節)定義的,如:

??? foo := a.o b.o c.o
??? bar := $(foo:%.o=%.c)

這依賴于被替換字串中的有相同的模式,模式中必須包含一個“%”字符,這個例子同樣讓$(bar)變量的值為“a.c b.c c.c”。


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

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

相關文章

第六篇:python基礎之文件處理

第六篇&#xff1a;python基礎之文件處理 閱讀目錄 一.文件處理流程二.基本操作2.1 文件操作基本流程初探2.2 文件編碼2.3 文件打開模式2.4 文件內置函數flush2.5 文件內光標移動2.6 open函數詳解2.7 上下文管理2.8 文件的修改一.文件處理流程 打開文件&#xff0c;得到文件句柄…

前端每日實戰:56# 視頻演示如何用純 CSS 描述程序員的生活

效果預覽 按下右側的“點擊預覽”按鈕可以在當前頁面預覽&#xff0c;點擊鏈接可以全屏預覽。 https://codepen.io/comehope/pen/YvYVvY 可交互視頻 此視頻是可以交互的&#xff0c;你可以隨時暫停視頻&#xff0c;編輯視頻中的代碼。 請用 chrome, safari, edge 打開觀看。 ht…

從特殊到一般-C#中的類

文章目錄類的概念類的定義實例例子分析類的成員數據成員屬性成員方法成員靜態成員博主寫作不容易&#xff0c;孩子需要您鼓勵 萬水千山總是情 , 先點個贊行不行 類的概念 在日常生活中&#xff0c;類是對具有相同特性的一類是物的抽象。比如水果是一個類&#xff0c;它是對…

Chapter 1 First Sight——30

The girl sitting there giggled. Id noticed that his eyes were black — coal black. 那個坐在那里的女孩笑著。我注意到她的眼睛是很色的--炭黑色的。 Mr. Banner signed my slip and handed me a book with no nonsense about introductions. Banner 先生簽了我的名字然后…

GPU 與CPU的作用協調,工作流程、GPU整合到CPU得好處

在不少人的心目中&#xff0c;顯卡最大的用途可能就只有兩點——玩游戲、看電影&#xff0c;除此之外&#xff0c;GPU并沒有其他的作用了。但是隨著微軟IE9的正式發布&#xff0c;不少人突然發現&#xff0c;微軟一直提到一個名詞&#xff1a;GPU硬件加速&#xff0c;從而也讓不…

[luoguP1029] 最大公約數和最小公倍數問題(數論)

傳送門 一.暴力枚舉&#xff08;加了點優化&#xff09; #include <cstdio>int x, y, ans;inline int gcd(int x, int y) {return !y ? x : gcd(y, x % y); }inline int lcm(int x, int y) {return x / gcd(x, y) * y; }int main() {int i, j;scanf("%d %d", …

CPU和GPU擅長和不擅長的方面

從它們執行運算的速度與效率的方面來探討這個論題。CPU和GPU都是具有運算能力的芯片&#xff0c; CPU更像“通才”——指令運算(執行)為重數值運算&#xff0c; GPU更像“專才”——圖形類數值計算為核心。在不同類型的運算方面的速度也就決定了它們的能力——“擅長和不擅長”…

一些IO流的知識

IO流&#xff1a; 輸入流&#xff1a;輸出流&#xff1a; 字節流&#xff1a;字符流&#xff1a;為了處理文字數據方便而出現的對象。 其實這些對象的內部使用的還是字節流(因為文字最終也是字節數據) 只不過&#xff0c;通過字節流讀取了相對應的字節數&#xff0c;沒有對這些…

為人示弱,做事留余 | 摸魚系列

我很喜歡結交有很好的自然觀察能力的朋友&#xff0c;這是種對周圍環境和文化的洞察力。 一方面的原因是優秀的領導者、企業家和投資人能利用這種能力發現新市場&#xff0c;預測新潮流&#xff0c;設計出有效的市場營銷活動&#xff0c;并找到需要重點關注的人群。 另一方面&a…

從一般到特殊-C#中的對象

文章目錄對象的概念對象的創建和使用匿名類型和初始化器構造函數和析構函數構造函數析構函數范例參數傳遞博主寫作不容易&#xff0c;孩子需要您鼓勵 萬水千山總是情 , 先點個贊行不行 對象的概念 類是具有相同特征一類事物的抽象&#xff0c;而對象是類的實例。 類和對象…

如何用面對對象來做一個躁動的小球?

今天來看看怎樣用面對對象來做一個躁動的小球。 首先我們先創建一個對象&#xff0c;他的屬性包含小球的隨機水平、縱向坐標&#xff0c;隨機寬、高&#xff0c;隨機顏色&#xff0c;以及創建小球的方法。 html: <div id"wrap"></div> js:function Boll(…

關于MyEclipse項目的名字的修改對項目導入導出的影響

不要修改項目名字&#xff0c;不管是在MyEclipse中(.project文件里面的額name會變)還是在G:\MyEclipseData目錄下(.project文件里面的額name不會變)&#xff0c;否則導入的時候不能訪問&#xff0c;會出現400的錯誤&#xff0c;而訪問的網址必須是以前沒改名前的那個名字才可以…

Gcc詳解以及靜態庫、動態庫生成

[轉] Gcc詳解以及靜態庫、動態庫生成 http://www.360doc.com/content/10/0619/14/1795182_33985297.shtml 1。gcc包含的c/c編譯器 gcc,cc,c,g,gcc和cc是一樣的&#xff0c;c和g是一樣的&#xff0c;(沒有看太明白前面這半句是什 么意思:))一般c程序就用gcc編譯&#xff0c;c程序…

改變世界的七大NLP技術,你了解多少?(上)

什么是NLP&#xff1f; 自然語言處理&#xff08;NLP&#xff09; 是計算機科學&#xff0c;人工智能和語言學的交叉領域。目標是讓計算機處理或“理解”自然語言&#xff0c;以執行語言翻譯和問題回答等任務。 隨著語音接口和聊天機器人的興起&#xff0c;NLP正在成為信息時代…

MINI類-結構體

文章目錄結構體的定義和使用實例類和結構體的關系博主寫作不容易&#xff0c;孩子需要您鼓勵 萬水千山總是情 , 先點個贊行不行 結構體與類相似&#xff0c;通常用來封裝小型的相關變量組&#xff0c;例如&#xff0c;學生的學號、姓名、性別、年齡等。結構是一種值類型&am…

由.def文件生成lib文件[轉]

最近在學習curl庫時&#xff0c;碰到一個問題&#xff0c;從官網上下載了一個lib版的&#xff0c;卻發現只有.dll&#xff0c;沒有lib文件&#xff0c;感覺很奇怪&#xff0c;google了之后才知道&#xff0c;原來庫作者的用意是讓用戶自己生成lib文件&#xff0c;下載到的lib文…

union 和 union all 有什么不同?

假設我們有一個表 Student&#xff0c; 包括以下字段與數據&#xff1a;drop table student;create table student( idint primary key,name nvarchar2(50) not null,score number not null);insert into student values(1,Aaron,78);insert into student values(2,Bill,76);in…

暴風影音硬件加速播放高清影片

近年來&#xff0c;高清視頻因為畫面清晰、視覺效果好&#xff0c;越來越受到眾多電腦用戶的厚愛。暴風影音3.6版本在高清的支持上&#xff0c;筆者必須得說&#xff0c;是暴風影音在高清方面的一個大跨越&#xff0c;在這個技術上&#xff0c;暴風把KMP等播放器都遠遠的拋在后…

SSL雙向認證的實現

2019獨角獸企業重金招聘Python工程師標準>>> 環境 系統&#xff1a;archlinux/centOS nginx&#xff1a;nginx/1.12.2 瀏覽器&#xff1a;火狐firefox 前提&#xff1a;1.安裝nginx。    2.安裝openssl。 生成證書 新建工作目錄 首先建立一個工作目錄&#x…

partial 分部類-龐大類的瘦身計劃

文章目錄使用情況語法博主寫作不容易&#xff0c;孩子需要您鼓勵 萬水千山總是情 , 先點個贊行不行 一般來說&#xff0c;一個類、結構或者接口位于一個源文件中&#xff0c;但是某些情況&#xff0c;比如大型項目、特殊部署時&#xff0c;可能需要把一個類、結構或者接口放…