1. make工具
利用make工具可以自動完成編譯工作,這些工作包括:
- 如果修改了某幾個源文件,則只重新編譯這幾個源文件
- 如果某個頭文件被修改了,則重新編譯所有包含該頭文件的源文件
利用這種自動編譯可以大大簡化開發工作,避免不必要的重新編譯。make工具通過一個稱為Makefile的文件來完成並自動維護編譯工作,Makefile文件描述了整個工程的編譯、連接規則。
2. Makefile文件
Makefile描述了整個工程的編譯連接規則。Makefile的基本規則為:
TARGET...: DEPENDENCIES...
COMMAND
...
- TARGER:目標程序產生的文件,如可執行文件和目標文件,目標也可以是要執行的動作,如clean,也稱為偽目標。
- DEPENDENCIES:依賴是用來產生目標的輸入文件列表,一個目標通常依賴與多個文件。
- COMMAND:命令是make執行的動作(命令是shell命令或是可在shell下執行的程序),注意每個命令行的起始字符必須為TAB字符。
- 如果DEPENDENCIES中有一個或多個文件更新的話,COMMAND就要執行,這就是Makefile最核心的內容。
3. Makefile的簡單示例
$ touch add.c add.h sub.c sub.h main.c
現在有這5個文件add.h 、sub.h中包含了函數聲明,add.c、sub.c中包含了函數實現,main.c調用了函數。Makefile的文件:
main:main.o add.o sub.o 【目標文件是main,它依賴於main.o,add.o,sub.o這三個文件】
gcc -Wall -g main.o add.o sub.o -o main 【由依賴文件生成目標文件應該執行的命令】
main.o:main.c
gcc -Wall -g -c main.c -o main.o
add.o:add.c add.h
gcc -Wall -g -c add.c -o add.o
sub.o:sub.c sub.h
gcc -Wall -g -c sub.c -o sub.o
保存Makefile文件后執行make命令:
$ make
gcc -Wall -g -c main.c -o main.o
gcc -Wall -g -c add.c -o add.o
gcc -Wall -g -c sub.c -o sub.o
gcc -Wall -g main.o add.o sub.o -o main
可以看到執行了make之后,由於 目標文件main依賴於 main.o add.o sub.o ,所以是需要先 生成 這三個.o文件,最后才生成main。
如果此時再次輸入make,會看到:
$ make
make: 'main' is up to date.
make的編譯規則是根據時間來進行判斷,一旦依賴列表中某個文件的更新時間比目標文件晚,則會重新生成目標,否則會出現以上提示。
默認情況下敲擊make將生成第一個目標,也就是main。也可以生成指定的目標:
$ make add.o 【指定只生成add.o文件】
Makefile文件的名字不一定得命名為“Makefile”或"makefile",使用其他名字也是可以的。例如我們由一個文件叫myMakefile,同樣可以使用它:
make -f myMakefile 【-f 選項的作用是把名字"myMakefile"作為makefile來對待。】
4. 偽目標
TARGET...: DEPENDENCIES...
COMMAND 【注意COMMAND之前是一個TAB,不是空格】
...
前面說過,TARGET除了可以是目標文件之外,還可以是偽目標。執行偽目標的效果等於執行了某一個動作, 並不產生目標文件。例如添加一個偽目標:
main:main.o add.o sub.o
gcc -Wall -g main.o add.o sub.o -o main
main.o:main.c
gcc -Wall -g -c main.c -o main.o
add.o:add.c add.h
gcc -Wall -g -c add.c -o add.o
sub.o:sub.c sub.h
gcc -Wall -g -c sub.c -o sub.o
clean : 【這是一個偽目標】
rm -f $(OBJECTS) main
使用make來執行偽目標:
$ make clean
rm -f main.o add.o sub.o main
可以看到make將執行偽目標下面的命令。
5. Makefile 自動化變量
從上面的Makefile文件我們發現一些問題:有時候目標文件的依賴列表過長,或者命令重復書寫。利用Makefile自動化變量可以解決這個問題。
選項名 | 作用 |
---|---|
$@ | 規則的目標文件名 |
$< | 規則的第一個依賴文件名 |
$^ | 規則的所有依賴文件列表 |
剛才的Makefile文件,我們可以改寫為:
main:main.o add.o sub.o
gcc -Wall -g $^ -o $@ 【等價於 gcc -Wall -g main.o add.o sub.o -o main】
main.o:main.c
gcc -Wall -g -c $< -o $@
add.o:add.c add.h
gcc -Wall -g -c $< -o $@
sub.o:sub.c sub.h
gcc -Wall -g -c $< -o $@
執行make,可以看到效果和之前是一樣的:
$ make
gcc -Wall -g -c main.c -o main.o
gcc -Wall -g -c add.c -o add.o
gcc -Wall -g -c sub.c -o sub.o
gcc -Wall -g main.o add.o sub.o -o main
還可以自定義變量:
OBJECTS = main.o add .o sub.o 【OBJECTS是自定義的變量名】
main:$(OBJECTS) 【可以在需要的地方使用變量名進行替換,替換規則為$(變量名)】
gcc -Wall -g $^ -o $@
main.o:main.c
gcc -Wall -g -c $< -o $@
add.o:add.c add.h
gcc -Wall -g -c $< -o $@
sub.o:sub.c sub.h
gcc -Wall -g -c $< -o $@
6. 編譯生成多個可執行文件
假設現在不只是想生成可執行main,還想生成可執行文件main2,可以這樣寫
BIN = main main2 【自定義變量BIN】
OBJECTS= main.o add.o sub.o
all : $(BIN) 【關注重點】
main : $(OBJECTS)
gcc -Wall -g $< -o $@
main2: $(OBJECTS)
gcc -Wall -g $< -o $@
main.o : main.c
gcc -Wall -g -c $< -o $@
main2.o :msin2.c
gcc -Wall -g -c $< -o $@
add.o:add.c add.h
gcc -Wall -g -c $< -o $@
sub.o:sub.c sub.h
gcc -Wall -g -c $< -o $@
clean :
rm -f $(OBJECTS) $(BIN)
為了生成目標文件all,需要先生成BIN,也即是 main main2。這樣就可以生成兩個可執行文件了。利用自定義變量可以再簡化這段Makefile文件:
BIN = main main2
OBJECTS= main.o add.o sub.o
CC = gcc
CFALGS = -Wall -g
all : $(BIN)
main : $(OBJECTS)
$(CC) $(CFALGS) $< -o $@
main2: $(OBJECTS)
$(CC) $(CFALGS) $< -o $@
main.o : main.c
$(CC) $(CFALGS) -c $< -o $@
main2.o :msin2.c
$(CC) $(CFALGS) -c $< -o $@
add.o:add.c add.h
$(CC) $(CFALGS) -c $< -o $@
sub.o:sub.c sub.h
$(CC) $(CFALGS) -c $< -o $@
clean :
rm -f $(OBJECTS) $(BIN)
但是這樣看起來,重復的內容還是比較多,可以使用下面的方法來繼續簡化:
BIN = main main2
OBJECTS= main.o add.o sub.o
CC = gcc
CFALGS = -Wall -g
all : $(BIN)
main : $(OBJECTS)
$(CC) $(CFALGS) $< -o $@
main2: $(OBJECTS)
$(CC) $(CFALGS) $< -o $@
.o .c : 【關注重點在這里】
$(CC) $(CFALGS) -c $< -o $@
clean :
rm -f $(OBJECTS) $(BIN)
利用 .o.c :,可以自動地把所有的.c文件到.o文件的生成都使用同一條命令來完成,簡化的重復的工作。
7. make常用的內嵌函數
首先看make中函數調用的形式:
//函數調用
$(function arguments) 【function是函數名稱,arguments是參數,使用$來調用】
值得注意的是,函數名稱與參數之間是空格。
來看三個常用make內嵌函數。
- $(wildcard PATTERN) 作用是在當前目錄下匹配模式的文件。
src = $(wildcard *.c) 【在當前目錄下搜索所有.c文件,文件名稱列表保存到src中】
- $(patsubst PATTENR,REPLACEMENT,TEXT) 模式替換函數,作用是把TEXT中文件列表從模式PATTENR替換為REPLACEMENT模式。
$(patsubst %.c,%.o,$src) 【把src中的.c文件列表中的文件從.c替換為.o】
等價於:
$(src:.c =.o) 【這種方式更常用】
- shell函數
shell函數可以執行shell下的命令,同樣是使用$來引用,例如
$(shell ls -d */) 【將當前目錄下的所有文件夾都列出來】
下面通過一個多級目錄的例子來使用這些函數。場景是這樣的,當前目錄下有main.c文件,同時還有若干個目錄,每個目錄中都有各自的.c文件。利用所有的.c文件編譯生成最后的main文件:
CC = gcc
CFLAGS = -Wall -g
BIN = main
SUBDIR = $(shell ls -d */) 【SUBDIR變量保存了子目錄的列表】
ROOTSRC = $(wildcard *.c) 【ROOTSRC保存了當前目錄下的.c文件列表】
ROOTOBJ = $(ROOTSRC:%.c = %.o) 【ROOTBOJ 保存了當前目錄下.c文件同名的.o列表】
SUBSRC = $(shell find $(SUBDIR) -name '*.c') 【SUBSRC 保存了所有子目錄下的的.c文件】
SUBOBJ = $(SUBSRC:%.c = %.o) 【SUBOBJ保存了所有子目錄下的.c文件同名的.o文件列表】
$(BIN):$(ROOTOBJ) $(SUBOBJ) 【main的生成依賴與當前目錄及所有子目錄下的.o文件】
$(CC) $(CFLAGS) -o $(BIN) $(ROOTOBJ) $(SUBOBJ)
.o .c:
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(BIN) $(ROOTOBJ) $(SUBOBJ)