GCC常用規則和Makefile通用規則解析
Makefile在編譯場景中經常會用到,以前對Makefile是一知半解,隻是知道怎麼添加檔案,怎樣編譯指定目标,内部是具體怎樣運作的,基本上不了解,這篇文章來闡述一下Makefile的基本規則,主要是linux編譯下面的Makefile規則。本文會先從GCC的簡單使用過渡到借助Makefile進行直接make。的如過有有問題的地方,歡迎在評論區留言一起讨論。
GCC編譯的基礎知識
一個或者多個C/C++檔案要經過預處理(preprocessing)、編譯(compilation)、彙編(assembly)和連結(linking)等4步才能變成可執行檔案。在linux編譯工程中我們一般使用的是GCC交叉編譯工具而不是直接使用GCC,交叉編譯工具的安裝也很簡單,直接使用
export
導出編譯器所在的路徑就行了,不建議直接更新在
~/bashrc
。
1、GCC常用編譯指令
arm-linux-gcc -o test test.c
指令會編譯test.c檔案,生成名稱為test的可執行檔案
該指令簡單,不用添加各種各樣的其他選項,甚至連
-o
選項也可以不需要。檔案數量少的時候可以直接使用,但是檔案多了的時候就不友善了。
gcc -Wp,-MD,abc.dep -c -o main.o main.c
對main.o進行預處理、編譯、彙編,但是不連結
同時生成一個main.c裡面包含的所有的頭檔案的清單檔案,命名為abc.dep
-Wp,-MD,abc.dep這個選項一般是在Makefile第一次編譯的時候用來生成每個檔案的頭檔案依賴清單,友善跟蹤後面頭檔案有變化的時候重新編譯涉及到的C檔案。
2、GCC常用選項
選項 | 解釋 |
---|---|
-c | 把預處理、編譯、彙編都做了,但是不連結 |
-o | 指定輸出檔案名稱 |
-I | 指定頭檔案目錄 |
-L | 指定連結時庫檔案目錄 |
-l | 指定連結哪一個庫檔案,-llibrary就是連結名為library的庫檔案 |
-D | 指定宏定義 |
-Wall | 打開警告選項 |
-g | 打開調試選項,生成可用于GDB調試的資訊 |
-O0 -O1 -O2 -O3 | 優化選項 |
-static | 靜态編譯,在支援動态連結(dynamic linking)的系統上,阻止連結共享庫 |
-Wp,-MD,abc.dep | 生成依賴的頭檔案,指令為abc.dep |
3、-Wp,-MD,abc.dep的二次說明
下面是一個簡單的代碼
#include <stdio.h>
int main(int argc, char* argv[])
{
printf("Main fun!\n");
return 0;
}
使用GCC進行編譯
gcc -Wp,-MD,abc.dep -o main main.c
,編譯完成後會在目前目錄生成main可執行檔案,以及abc.dep頭檔案依賴清單檔案,使用
cat adb.dep
來檢視一下檔案内容
main.o: main.c /usr/include/stdc-predef.h /usr/include/stdio.h
/usr/include/x86_64-linux-gnu/bits/libc-header-start.h
/usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h
/usr/include/x86_64-linux-gnu/bits/wordsize.h
/usr/include/x86_64-linux-gnu/bits/long-double.h
/usr/include/x86_64-linux-gnu/gnu/stubs.h
/usr/include/x86_64-linux-gnu/gnu/stubs-64.h
/usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h
/usr/include/x86_64-linux-gnu/bits/types.h
/usr/include/x86_64-linux-gnu/bits/typesizes.h
/usr/include/x86_64-linux-gnu/bits/types/__FILE.h
/usr/include/x86_64-linux-gnu/bits/types/FILE.h
/usr/include/x86_64-linux-gnu/bits/libio.h
/usr/include/x86_64-linux-gnu/bits/_G_config.h
/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h
/usr/lib/gcc/x86_64-linux-gnu/7/include/stdarg.h
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h
/usr/include/x86_64-linux-gnu/bits/sys_errlist.h
GCC的基礎知識到這裡就可以了,如果你感興趣的話,可以去百度GUN線上手冊去了解,或者百度《GNU Make 使用手冊》進行了解。
Makefile的相關知識
如果你去百度通用Makefile,會出現很多各種各樣的Makefile程式,他們似乎大同小異,但是很多都是沒有注釋,看的讓人一臉懵,有寫地方都看不懂。Makefile的相關知識也可以參考《GNU Make 使用手冊》進行了解。
Makefile的樣式
目标(target)…: 依賴(prerequiries)…
<tab>指令(command)
指令被執行的2個條件:
- 依賴檔案比目标檔案
- 目标檔案還沒生成。
Makefile的常用函數
1、foreach
$(foreach var,list,text)
簡單地說,就是對list中的每一個元素,取出來賦給var,然後把var改為text所描述的形式。
objs := a.o b.o
dep_files := $(foreach f, $(objs), .$(f).d)
// 最終 dep_files := .a.o.d .b.o.d
2、wildcard
$(wildcard pattern)
pattern所列出的檔案是否存在,把存在的檔案都列出來。
src_files := $( wildcard *.c)
// 最終 src_files中列出了目前目錄下的所有.c檔案
3、filter
$(filter pattern…,text)
把text中符合pattern格式的内容,filter(過濾)出來、留下來。
obj-y := a.o b.o c/ d/
DIR := $(filter %/, $(obj-y))
//結果為:c/ d/
4、filter-out
$(filter-out pattern…,text)
把text中符合pattern格式的内容,filter-out(過濾)出來、扔掉。
obj-y := a.o b.o c/ d/
DIR := $(filter-out %/, $(obj-y))
//結果為:a.o b.o
5、patsubst
$(patsubst pattern,replacement,text)
尋找
pattern’的字,用
text’中符合格式
pattern’和`replacement’中可以使用通配符。
replacement’替換它們。
subdir-y := c/ d/
subdir-y := $(patsubst %/, %, $(subdir-y))
// 結果為:c d
6、shell指令
TOPDIR := $(shell pwd)
這是個立即變量,TOPDIR等于shell指令pwd的結果。
rm -f $(shell find -name "*.o")
// 該指令會遞歸删除該目錄下面所有的.o檔案
7、指定Makefile檔案指令
我們可以使用“-f”選項指定檔案,不再使用名為“Makefile”的檔案
make -f Makefile.build
我們可以使用“-C”選項指定目錄,切換到其他目錄裡去
make -C a/ -f Makefile.build
// 用a目錄下面的Makefile.build進行編譯
我們可以指定目标,不再預設生成第一個目标:
make -C a/ -f Makefile.build other_target
Makefile中自動化變量
- $@ 目标
- $< 依賴目标中的第一個目标名字。如果依賴目标是以模式(即 % )定義的,那麼 $< 将是符合模式的一系列的檔案集。注意,其是一個一個取出來的
- $^ 所有的依賴目标的集合。以空格分隔。如果在依賴目标中有多個重複的,那個這個變量會去除重複的依賴目标,隻保留一份。
Makefile中變量的導出
變量使用
export
進行導出,導出了之後其他Makefile都可以擷取到這個變量。
Makefile中的假想目标
我們的Makefile中有這樣的目标:
clean:
rm -f $(shell find -name “*.o”)
rm -f $(TARGET)
如果目前目錄下恰好有名為“clean”的檔案,那麼執行“make clean”時它就不會執行那些删除指令。
這時我們需要把“clean”這個目标,設定為“假想目标”,這樣可以確定執行“make clean”時那些删除指令肯定可以得到執行。
使用下面的語句把“clean”設定為假想目标:
.PHONY : clean
Makefile變量的含義
A = xxx // 延時變量
B ?= xxx // 延時變量,隻有第一次定義時指派才成功;如果曾定義過,此指派無效
C := xxx // 立即變量
D += yyy // 如果D在前面是延時變量,那麼現在它還是延時變量
// 如果D在前面是立即變量,那麼現在它還是立即變量
A = $@
test:
@echo $A #使用到的時候$@(目标)是test
變量A的值在執行時才确定,它等于test,是延時變量
A := $@
test:
@echo $A
變量A的值在立即确定,它等于空,是立即變量
變量的含義似乎用處不大,或者說我還沒找到實際的用處,歡迎大家進行指正說明。
頂層Makefile
CROSS_COMPILE = #這裡需要填入你用的gcc交叉編譯工具,例如gcc-linux-arm-
AS = $(CROSS_COMPILE)as #指定彙編工具
LD = $(CROSS_COMPILE)ld #指定連結工具
CC = $(CROSS_COMPILE)gcc #指定gcc編譯工具
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
export AS LD CC CPP AR NM #導出一些變量
export STRIP OBJCOPY OBJDUMP
CFLAGS := -Wall -O2 -g # -Wall 打開警告資訊 -O2 2級優化 -g 産生調試資訊,可以不要
CFLAGS += -I $(shell pwd)/include #指定頭檔案目錄
LDFLAGS := #連結标記,可以填入你需要的連結标記
export CFLAGS LDFLAGS #導出
#擷取頂部目錄,并且導出
TOPDIR := $(shell pwd)
export TOPDIR
TARGET := test #目标名字,以後編譯生成的可執行檔案的名字就是test
obj-y += main.o #這裡指定目前檔案需要編譯的C檔案
obj-y += sub.o
obj-y += a/ #這裡指定目前檔案夾下面需要遞歸編譯的子檔案夾
all : start_recursive_build $(TARGET) #第一個目标,直接執行make的預設目标,它有兩個依賴
@echo $(TARGET) has been built!
start_recursive_build: #第一個依賴,生成的指令是指定makefile進行編譯
make -C ./ -f $(TOPDIR)/Makefile.build
$(TARGET) : built-in.o #第二個依賴是把目前檔案夾下面的built-in.o,連結成為一個可執行檔案
$(CC) -o $(TARGET) built-in.o $(LDFLAGS)
clean: #清空,假想目标,無條件執行
rm -f $(shell find -name "*.o")
rm -f $(TARGET)
distclean: #删除目錄下面所有.o檔案,假想目标
rm -f $(shell find -name "*.o")
rm -f $(shell find -name "*.d")
rm -f $(TARGET)
.PHONY: clean
.PHONY: distclean
頂層Makefile的一個作用是導出各種變量,第二個作用是設定了一個目标
all
,它有兩個依賴,第一個依賴
start_recursive_build
就是去執行make檔案
Makefile.build
,第二個依賴把目前目錄的
build-in.o
連結生成
test
這個可執行檔案。
Makefile.build檔案解析
PHONY := __build #定義一個PHONY變量,立即變量
__build: #設定一個目标,使其成為Makefile.build的第一個目标,注意,最後一行把這個目标設定為僞目标
obj-y := #立即變量,清空
subdir-y := #立即變量,清空
EXTRA_CFLAGS :=
include Makefile #包含了目前目錄的Makefile,在Makefile中我們定義了obj-y += xxx(main.o compress/ ...... ),以及EXTRA_CFLAGS等變量,這裡會全部導入進來
# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y)) : c/ d/
# __subdir-y : c d
# subdir-y : c d
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y))) #篩選出目前目錄的目标變量中的子目錄,并且去掉/,在這裡我們獲得了子目錄的名字
subdir-y += $(__subdir-y) #擷取所有檔案子目錄名稱,指派給subdir-y
# 擷取到所有目前子目錄的build-in.o c/built-in.o d/built-in.o #built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o) #對于subdir-y裡面的每一個值(目錄),增加一個相應的目錄/built-in.o的變量值
# a.o b.o
cur_objs := $(filter-out %/, $(obj-y)) #擷取目前目錄下面的所有的a.o b.o之類的檔案,去掉檔案夾
dep_files := $(foreach f,$(cur_objs),.$(f).d) #擷取目前檔案的頭檔案依賴,
dep_files := $(wildcard $(dep_files)) #挑選真正存在的依賴檔案,在第一次編譯的時候才會将這些檔案生成,
# 第一次編譯的時候,dep_files變量肯定是空的,因為根本沒有.main.o.d之類的檔案存在,但是第一次編譯之後,就會通過-Wp,-MD,$(dep_file)生成這些.main.o.d類型的檔案了
# 第一次編譯肯定會編譯到所有的頭檔案
# 第二次以及以後的編譯,就有.main.o.d之類的檔案存在了,他們會作為依賴,修改了頭檔案,涉及到的C檔案會被重新編譯了
ifneq ($(dep_files),)
include $(dep_files) #導入真正存在的依賴檔案,第一次編譯該變量是空的,啥也導入不進來
endif
PHONY += $(subdir-y) #子目錄檔案夾名稱也作為一個依賴
__build : $(subdir-y) built-in.o # 第一個規則,有兩個依賴,一個$(subdir-y)子目錄名稱,另一個是目前目錄的build-in.o
$(subdir-y): #子目錄檔案名依賴,就再去子目錄裡面編譯一下,生成build-in.o檔案。如果沒有子目錄的話,這個指令就不執行了。
make -C $@ -f $(TOPDIR)/Makefile.build # 依次進入該子目錄變量裡面存儲的值,使用的Makefile.build進行編譯,這裡是應用了遞歸的思想了
built-in.o : $(cur_objs) $(subdir_objs) # 目前目錄的build-in.o的生成規則。第一個依賴是目前目錄下面的所有.o檔案,第二個規則是所有子目錄下面的build-in.o檔案
$(LD) -r -o $@ $^ # 該規則的指令:将該目錄下的.o和$(subdir_obj)打包(連結)成built-in.o檔案
dep_file = [email protected] #第一次編譯會生成這個隐藏檔案,這個是.c檔案的頭檔案依賴清單
%.o : %.c #目前目錄下面的.o檔案的生成規則
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
# EXTRA_CFLAGS是對目前目錄下面所有的.c檔案指定的額外的編譯選項,在目前層的Makefile中指定
# CFLAGS_$@是對目前目錄下面的指定的.c檔案指定的額外的的編譯選項,在目前層的Makefile中指定,形式如CFLAGS_main.o := -D DEBUG_SUB3
# -c 把預處理、編譯、彙編都做了,但是不連結
.PHONY : $(PHONY) # 将PHONY聲明為僞目标,無條件執行
Makefile.build就是一個在整個工程下面的通用Makefile檔案,每一級目錄的編譯都會調用這個檔案來進行編譯,每一級的目錄下面的Makefile功能隻是提供obj-y以及額外的編譯選項,他會被Makefile.build導入進去。Makefile.build使用的編譯方法是
遞歸調用
,他會把子目錄生成的build-in.o以及目前目錄的所有.o檔案全部連接配接成文目前目錄的build-in.o,最後一路連結到頂層的build-in.o之後,就可以連結成為一個可執行檔案。
我上傳了demo工程,可以使用demo工程進行進一步加深印象。
參考連結:韋東山Makefile系列知識教程