天天看點

GCC常用規則和Makefile通用規則解析GCC常用規則和Makefile通用規則解析

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)

尋找

text’中符合格式

pattern’的字,用

replacement’替換它們。

pattern’和`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系列知識教程