編譯概述
編譯基礎:
使用GCC編譯程序時可以分為4個階段:
| (1)預處理(pre-processing) | -E | .c---->.i | -I (Include) | 將源文件生成中間文件 |
|---|---|---|---|---|
| (2)編譯(compiling) | -S | .i---->.s | 將中間文件生成匯編 | |
| (3)匯編(Assembling) | -c | .s--->.o | 將匯編轉(zhuǎn)換成機器代碼 | |
| (4)鏈接(Linking) | .o--->可執(zhí)行文件 | -L(Link) | 匯集成可執(zhí)行文件 |
基本用法:
gcc 【options】 【filenames】
常用選項:
| -c | 只是編譯,不生成可執(zhí)行文件,將.c文件生成.o文件 |
|---|---|
| -o outputfile | 確定輸出文件的名字為outputfile |
| -g | 產(chǎn)生gdb所需要的符號信息,用于對源代碼的調(diào)試 |
| -O | 優(yōu)化編譯鏈接,編譯鏈接時間會比較慢 |
| -O2 | 比-O更好的優(yōu)化編譯鏈接,編譯鏈接時間會更加慢 |
| -Wall | 輸出所有警告信息 |
| -w | 關(guān)閉所有警告信息 |
| -Idirname | 將dirname的內(nèi)容加入到程序頭文件目錄列表中,在預處理階段使用。I意指Include |
| -Ldirmane | 將dirname的目錄加入到程序的庫文件搜索目錄列表中,這是鏈接中使用的參數(shù)。L意指Link |
makefile:
makefile文件和make工具一起使用,用于控制工程項目的編譯和鏈接,也可以用來編寫手冊頁和程序的安裝。
make工具用于解釋執(zhí)行makefile文件中的內(nèi)容。
makefile文件中通常包含源文件和目標文件的依賴關(guān)系以及從源文件生成目標文件的規(guī)則。
make工具可以根據(jù)makefile判斷哪些文件需要被重新編譯,目標文件的構(gòu)建順序等。
規(guī)則:
在講述makefile之前,先來粗略地看一看makefile的規(guī)則。
target ... : prerequisites ...
command
...
...
target:可以是一個object file(目標文件),也可以是一個執(zhí)行文件,還可以是一個標簽(label)。對于標簽這種特性,在后續(xù)的“偽目標”章節(jié)中會有敘述。
prerequisites:生成該target所依賴的文件和/或target
command:該target要執(zhí)行的命令(任意的shell命令),一定要以tab開頭
這是一個文件的依賴關(guān)系,也就是說,target這一個或多個的目標文件依賴于prerequisites中的文件,其生成規(guī)則定義在command中。即prerequisites中如果有一個以上的文件比target文件要新的話,command所定義的命令就會被執(zhí)行。
這就是makefile的規(guī)則,也就是makefile中最核心的內(nèi)容。
靜態(tài)模式:
靜態(tài)模式可以更加容易地定義多目標的規(guī)則,可以讓我們的規(guī)則變得更加的有彈性和靈活。我們還是先來看一下語法:
<targets ...> : <target-pattern> : <prereq-patterns ...>
<commands>
...
例子如下:
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
如果我們的 %.o 有幾百個,靜態(tài)模式規(guī)則可以寫完一堆規(guī)則,實在是太有效率了。
同名目標:
target1: dep1
target1: dep2
cmd2
這種情況下,這兩個相同的target1會被合并成:
target1: dep1 dep2
cmd2
但如果第一條規(guī)則本身也帶一個命令的話, makefile就無法合并, 給出警告,并用后面的規(guī)則替代前面的規(guī)則:
target1: dep1
cmd1
target1: dep2
cmd2
最后生成的是, 其實就是后一條替代了前一條,然后給出警告:
target1: dep2
cmd2
可以參考Makefile 的重復目標-arley-ChinaUnix博客
常用函數(shù):
函數(shù)調(diào)用,很像變量的使用,也是以 $ 來標識的,其語法如下:
$(<function> <arguments>)
#或者
${<function> <arguments>}
這里, 就是函數(shù)名,make支持的函數(shù)不多。 為函數(shù)的參數(shù),參數(shù)間以逗號 , 分隔,而函數(shù)名和參數(shù)之間以“空格”分隔。函數(shù)調(diào)用以 $ 開頭,以圓括號或花括號把函數(shù)名和參數(shù)括起。感覺很像一個變量,是不是?函數(shù)中的參數(shù)可以使用變量,為了風格的統(tǒng)一,函數(shù)和變量的括號最好一樣,如使用 $(subst a,b,$(x)) 這樣的形式,而不是 $(subst a,b, ${x}) 的形式。因為統(tǒng)一會更清楚,也會減少一些不必要的麻煩。
patsubst
格式:$(patsubst pattern,replacement,text)
名稱:模式字符串替換函數(shù)
功能:查找text中的單詞(單詞以“空格”、“Tab”或“回車”“換行”分隔)是否符合模式pattern,如果匹配的話,則以replacement替換。這里,pattern可以包括通配符“%”,表示任意長度的字串。如果replacement中也包含“%”,那么,replacement中的這個“%”將是pattern中的那個“%”所代表的字串。(可以用“\”來轉(zhuǎn)義,以“%”來表示真實含義的“%”字符)
返回:函數(shù)返回被替換過后的字符串。
示例:$(patsubst %.c,%.o, a.c b.c)把字串“a.c b.c”符合模式[%.c]的單詞替換成[%.o],返回結(jié)果是“a.o b.o”
notdir
格式:$(notdir names)
名稱:將參數(shù)中的路徑去掉
功能:從文件名序列<names>中取出非目錄部分。非目錄部分是指最后一個反斜杠(“/”)之后的部分。
返回:返回文件名序列<names>的非目錄部分。
示例:$(notdir src/foo.c hacks)返回值是“foo.c hacks”。
wildcard
格式:$(wildcard PATTERN...)
功能:擴展通配符
示例:$(wildcard .c ./foo/.c) 返回值是“foo.c hacks”。搜索當前目錄及./foo/下所有以.c結(jié)尾的文件,生成一個以空格間隔的文件名列表。
foreach
格式:$(foreach <var>,<list>,<text>)
名稱:用來做循環(huán)用
功能:這個函數(shù)的意思是,把參數(shù) 中的單詞逐一取出放到參數(shù) 所指定的變量中,然后再執(zhí)行 所包含的表達式。每一次 會返回一個字符串,循環(huán)過程中, 的所返回的每個字符串會以空格分隔,最后當整個循環(huán)結(jié)束時, 所返回的每個字符串所組成的整個字符串(以空格分隔)將會是foreach函數(shù)的返回值。
示例:
names := a b c d
files := $(foreach n,$(names),$(n).o)
上面的例子中, $(name) 中的單詞會被挨個取出,并存到變量 n 中, $(n).o 每次根據(jù) $(n) 計算出一個值,這些值以空格分隔,最后作為foreach函數(shù)的返回,所以, $(files) 的值是 a.o b.o c.o d.o 。
info
格式:$(info text...)
功能:打印處text的內(nèi)容,相當于printf,常用于調(diào)試
示例:$(info "some text")打印 "some text"
filter
格式:$(filter suffix…,$(SOURCES))
功能:目標串中找出符合匹配規(guī)則的
示例:
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
#使用“$(filter %.c %.s,$(sources))”的返回值給 cc 來編譯生成目標“foo”,函數(shù)返回
#值為“foo.c bar.c baz.s”
filter-out
格式:$(filter-out SUFFIX…,$(SOURCES))
功能:從目標串中過濾掉符合匹配規(guī)則的
示例:
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
$(filter-out $(mains),$(objects))
#實現(xiàn)了去除變量“objects”中“mains”定義的字串(文件名)功能。它的返回值
#為“foo.o bar.o”。
自動變量:
$@ :表示目標文件
$^ :表示所有的依賴文件
$< :表示第一個依賴文件
$? :表示比目標還要新的依賴文件列表
目標變量:
為某個目標設置局部變量,這種變量被稱為“Target-specific Variable”,它可以和“全局變量”同名,因為它的作用范圍只在這條規(guī)則以及連帶規(guī)則中,所以其值也只在作用范圍內(nèi)有效。而不會影響規(guī)則鏈以外的全局變量的值。
其語法是:
<target ...> : <variable-assignment>;
<target ...> : overide <variable-assignment>
variable-assignment;可以是前面講過的各種賦值表達式,如 = 、 := 、 += 或是 ?= 。第二個語法是針對于make命令行帶入的變量,或是系統(tǒng)環(huán)境變量。
這個特性非常的有用,當我們設置了這樣一個變量,這個變量會作用到由這個目標所引發(fā)的所有的規(guī)則中去。如:
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.o
prog.o : prog.c
$(CC) $(CFLAGS) prog.c
foo.o : foo.c
$(CC) $(CFLAGS) foo.c
bar.o : bar.c
$(CC) $(CFLAGS) bar.c
在這個示例中,不管全局的 $(CFLAGS) 的值是什么,在prog目標,以及其所引發(fā)的所有規(guī)則中(prog.o foo.o bar.o的規(guī)則), $(CFLAGS) 的值都是 -g。
還有一個具體的示例:recipes/Makefile at master · chenshuo/recipes (github.com)
學以致用:
原始版本:
main.out:main.o tool1.o tool2.o
gcc main.o tool1.o tool2.o -o main.out
main.o:main.c
gcc main.c -c -o main.o
tool1.o:tool1.c
gcc tool1.c -c -o tool1.o
tool2.o:tool2.c
gcc tool2.c -c -o tool2.o
PHONY:clean
clean:
rm -rf *.o *.out
加入變量:
OBJS = main.o tool1.o tool2.o
CC = gcc
CFLAGS += -c -Wall -g
main.out:$(OBJS)
$(CC) $(OBJS) -o main.out
main.o:main.c
$(CC) main.c $(CFLAGS) -o main.o
tool1.o:tool1.c
$(CC) tool1.c $(CFLAGS) -o tool1.o
tool2.o:tool2.c
$(CC) tool2.c $(CFLAGS) -o tool2.o
clean:
$(RM) *.o *.out -r
自動變量:
OBJS = main.o tool1.o tool2.o
CC = gcc
CFLAGS += -c -Wall -g
main.out:$(OBJS)
$(CC) $(OBJS) -o $@
main.o:main.c
$(CC) $^ $(CFLAGS) -o $@
tool1.o:tool1.c
$(CC) $^ $(CFLAGS) -o $@
tool2.o:tool2.c
$(CC) $^ $(CFLAGS) -o $@
clean:
$(RM) *.o *.out -r
隱含規(guī)則:
OBJS = main.o tool1.o tool2.o
CC = gcc
CFLAGS += -c -Wall -g
main.out:$(OBJS)
$(CC) $(OBJS) -o $@
%.o:%.c
$(CC) $^ $(CFLAGS) -o $@
clean:
$(RM) *.o *.out -r
all:clean main.out
@echo "clean first then compile then link"
@echo $(OBJS)
OBJS := $(patsubst %.c,%.o,$(wildcard *.c))
CC = gcc
CFLAGS += -c -Wall -g
main.out:$(OBJS);@echo "link"
$(CC) $^ -o $@
%.o:%.c;@echo "complie"
$(CC) $^ $(CFLAGS) -o $@
PHONY:clean
clean:
$(RM) *.o *.out -r
練習題:
given:
all:ef cd
@echo 123
cd:
@echo 456
ef:
@echo 789
輸出:
789
456
123
given:
all:
@echo 123
cd:
@echo 456
ef:
@echo 789
輸出 :
123
given:
all:clean main.out
@echo "clean first then compile then link"
main.out:tool1.o tool2.o main.o
@echo "link"
gcc main.o tool1.o tool2.o -o main.out
main.o:main.c
@echo "link"
gcc main.c -c -o main.o
tool1.o:tool1.c
@echo "link"
gcc tool1.c -c -o tool1.o
tool2.o:tool2.c
@echo "link"
gcc tool2.c -c -o tool2.o
PHONY:clean
clean:
rm -rf *.o *.out
輸出:
rm -rf *.o *.out
link
gcc tool1.c -c -o tool1.o
link
gcc tool2.c -c -o tool2.o
link
gcc main.c -c -o main.o
link
gcc main.o tool1.o tool2.o -o main.out
clean first then compile then link
最佳實踐:
工程目錄如下,請使用makefile構(gòu)建工程:
├── include
│ ├── base
│ │ ├── BaseTypes.h
│ │ ├── InterfaceDef.h
│ │ ├── Keywords.h
│ │ └── stdc.h
│ ├── BaseMacro.h
│ ├── Cent.h
│ ├── common.h
│ └── Dollar.h
├── Makefile
├── README.md
├── src
│ ├── Cent.cpp
│ ├── common.cpp
│ └── Dollar.cpp
└── test
├── CentTest.cpp
├── DollarTest.cpp
├── FormatTest.cpp
└── main.cpp
https://github.com/yanxicheung/usd
Q&A:
一個Makefile如何生成若干個可執(zhí)行文件?
如果你的Makefile需要一口氣生成若干個可執(zhí)行文件,但你只 想簡單地敲一個make完事,并且,所有的目標文件都寫在一個Makefile中,那么你可以使用“偽目標”這個特性:
all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
Makefile中的第一個目標會被作為其默認目標。我們聲明了一個“all”的偽目標,其依賴于其它三個目標。
由于默認目標的特性是,總是被執(zhí)行的,但由于“all”又是一個偽目標,偽目標只是一個標簽不 會生成文件,所以不會有“all”文件產(chǎn)生。
于是,其它三個目標的規(guī)則總是會被決議。也就達到了我們一口 氣生成多個目標的目的。 .PHONY : all 聲明了“all”這個目標為“偽目標”。
(注:這里的顯式 “.PHONY : all” 不寫的話一般情況也可以正確的執(zhí)行,這樣make可通過隱式規(guī)則推導出, “all” 是一 個偽目標,執(zhí)行make不會生成“all”文件,而執(zhí)行后面的多個目標。建議:顯式寫出是一個好習慣。)