本文为Makefile的学习笔记,学习的素材主要为Bilibili UP主李呵欠的“GUN Makefile编译C/C++教程”。笔记中实验的平台为Window+VScode,实验的实现和李呵欠UP主的视频素材有所差别。
源文件.c -> 预编译文件.i -> 汇编语言.s -> 目标文件.o -> 可执行文件(自定义名称,无需后缀)
gcc -E[.c源文件](可选选项: -o [自定义文件名])
例:
gcc -E main.c #-E告诉编译器只进行预处理工作,默认不生成“.i”文件;
gcc -E main.c -o objectFile.i #-o objectFile.i告诉编译器预处理后生成“objectFile.i”文件
gcc -S[.c源文件](可选选项: -o [自定义文件名])
例:
gcc -S main.c #-S告诉编译器进行预处理和编译成汇编语言,默认生成“main.s”文件;
gcc -S main.c -o objectFile.s #-o objectFile.s告诉编译器生成的汇编文件命名为“objectFile.s”
gcc -c [.c] [.c] ...(可选选项: -o [自定义文件名])
例:
gcc -c main.c #-c告诉编译器生成二进制的目标文件,默认生成“main.o”文件;
gcc -c main.c -o objectFile.o #-o objectFile.o告诉编译器编译生成的目标文件命名为“objectFile.o”
gcc -c main.c src1.c src2.c #编译多个.c文件
gcc [.c] [.c] ... (可选选项: -o [自定义文件名])
例:
gcc main.c #告诉编译器生成二进制的目标文件,默认生成“a.out”文件;
gcc main.c -o exec #-o exec 告诉编译器编译生成的可执行文件命名为“exec.out”
gcc main.c src1.c src2.c -o exec #多个源文件生成一个可执行文件
./exec #运行可执行文件exec
第一步:将源文件编译成.o文件
gcc -c [.c] [.c] ...
第二步:编静态库,目标文件一般命名为:“lib+自定义库名”,目标文件[lib自定义库名.a]在依赖文件[.o]之前
ar -r [lib自定义库名.a] [.o] [.o]...
第三步:链接成可执行文件
gcc [.c] [.a] (可选选项: -o [自定义可执行文件名])
gcc [.c] -o [自定义可执行文件名] -l[库名] -L[库所在路径]
例:
gcc -c src1.c src2.c src2.c
ar -r libsrc.a src1.o src2.o src3.o
gcc main.c libsrc.a -o exec
./exec #运行可执行文件exec
第一步:将源文件编译成.o文件
gcc -c -fpic [.c] [.c] ...
第二步:编库,目标文件一般命名为:“lib+自定义库名”,目标文件[lib自定义库名.so]在依赖文件[.o]之后
gcc -shared [.o] [.o]... -o [lib自定义库名.so]
第三步:链接动态库到可执行文件
gcc [.c] -o [自定义可执行文件名] -l[库名] -L[库路径] -Wl,-rpath=[库路径]
例:
gcc -c -fpic src1.c src2.c src2.c
gcc -shared src1.o src2.o src3.o -o libsrc.so
gcc main.c -o exec -lsrc -L库所在路径 #-lsrc为-l[库名]
./exec #运行可执行文件exec
向下支持C源文件编译,与C语言的gcc指令基本一样,将gcc改为g++。
g++ -E[.c源文件/.cpp源文件](可选选项: -o [自定义文件名])
例:
g++ -E main.cpp #-E告诉编译器只进行预处理工作,默认不生成“.i”文件;
g++ -E main.cpp -o objectFile.i #-o objectFile.i告诉编译器预处理后生成“objectFile.i”文件
g++ -S[.c源文件/.cpp源文件](可选选项: -o [自定义文件名])
例:
g++ -S main.c #-S告诉编译器进行预处理和编译成汇编语言,默认生成“main.s”文件;
g++ -S main.cpp -o objectFile.s #-o objectFile.s告诉编译器生成的汇编文件命名为“objectFile.s”
g++ -c [.c/cpp] [.c/cpp] ...(可选选项: -o [自定义文件名])
例:
g++ -c main.cpp #-c告诉编译器生成二进制的目标文件,默认生成“main.o”文件;
g++ -c main.cpp -o objectFile.o #-o objectFile.o告诉编译器编译生成的目标文件命名为“objectFile.o”
g++ -c main.cpp src1.cpp src2.c #编译多个.c文件
g++ [.c/cpp] [.c/cpp] ... (可选选项: -o [自定义文件名])
例:
g++ main.cpp #告诉编译器生成二进制的目标文件,默认生成“a.out”文件;
g++ main.cpp -o exec #-o exec 告诉编译器编译生成的可执行文件命名为“exec.out”
g++ main.cpp src1.c src2.c -o exec #多个源文件生成一个可执行文件
./exec #运行可执行文件exec
第一步:将源文件编译成.o文件
g++ -c [.c/cpp] [.c/cpp] ...
第二步:编静态库,目标文件一般命名为:“lib+自定义库名”,目标文件[lib自定义库名.a]在依赖文件[.o]之前
ar -r [lib自定义库名.a] [.o] [.o]...
第三步:链接成可执行文件
g++ [.c/cpp] [.a] (可选选项: -o [自定义可执行文件名])
g++ [.c/cpp] -o [自定义可执行文件名] -l[库名] -L[库所在路径]
例:
g++ -c src1.cpp src2.cpp src2.c
ar -r libsrc.a src1.o src2.o src3.o
g++ main.cpp libsrc.a -o exec
./exec #运行可执行文件exec
//第一步:将源文件编译成.o文件
g++ -c -fpic [.c/cpp] [.c/cpp] ...
//第二步:编库,目标文件一般命名为:“lib+自定义库名”,目标文件[lib自定义库名.so]在依赖文件[.o]之后
g++ -shared [.o] [.o]... -o [lib自定义库名.so]
//第三步:链接动态库到可执行文件
g++ [.c/cpp] -o [自定义可执行文件名] -l[库名] -L[库路径] -Wl,-rpath=[库路径]
例:
g++ -c -fpic src1.cpp src2.c src2.c
g++ -shared src1.o src2.o src3.o -o libsrc.so
g++ main.cpp -o exec -lsrc -L库所在路径 #-lsrc为-l[库名]
./exec #运行可执行文件exec
目标文件 : 依赖文件(单个或多个)
[tab键]命令
目标文件:可以是.o文件、可执行文件、还可以是标签(伪目标)
依赖文件:生成目标文件所必需的文件
命令:生成目标文件所执行命令
两个最简单的Makefile
debug :
@echo hello
debug :
echo hello
Windows平台测试
Windows shell>>>>>>>
mingw32-make debug
hello
Windows shell>>>>>>>
mingw32-make debug
echo hello
hello
Linux平台测试
Linux Terminal>>>>>>>
make debug
hello
Linux Terminal>>>>>>>
make debug
echo hello
hello
伪目标不是目标文件,它是一个标签,make时要显式地指明才能生效标签后的指令,
如:make debug。
伪目标不能与文件重名,一般为了避免重名用“.PHONY”来指明伪目标,
如:.PHONY : debug
Makefile中的变量类似于C语言中的宏,在声明时给予初值。在使用时使用“ ( 变量名 ) ”,它表示文件执行时,“ (变量名)”,它表示文件执行时,“ (变量名)”,它表示文件执行时,“(变量名)”由变量值替代。
#定义:
变量名 := 变量值 #变量值为字符串
#引用:
$(变量名)或${变量名}
例:
cpp := src/main.cpp
obj := obj/main.o
$(obj) : ${cpp}
g++ -c ${cpp} -o $(obj) #等价于:g++ -c src/main.cpp -o obj/main.o
=
- 赋值运算符,右边的值分配给左边的变量
- 使用该运算符赋值的变量,一旦右边的值在下文发生了更新,被赋值变量的值随之改变
Default_Name = DOG
Tentative_Name = $(Default_Name)
Final_Name = $(Tentative_Name)
Default_Name = CAT
debug :
echo $(Tentative_Name) $(Final_Name)
#make debug结果:
echo CAT CAT
CAT CAT
注意“=”不能用于被赋值变量同时在等号右边被引用的情况(会发生递归陷阱)
例:
src = src/main.cpp src/src1.cpp src/src2.cpp
obj = $(subst src/,obj/,$(src)) #subst函数的使用参考3.6.3
obj = $(subst .cpp,.o,$(obj))
Makefile:3: *** Recursive variable 'obj' references itself (eventually). Stop.
:=
- 立即赋值运算符,用于定义变量是立即求值
- 该值一旦定义不再改变,即使右值在下文中发生了改变
Default_Name = DOG
Tentative_Name := $(Default_Name)
Final_Name = $(Default_Name)
Default_Name = CAT
debug :
echo $(Tentative_Name) $(Final_Name)
#make debug结果:
echo DOG CAT
DOG CAT
?=
- 默认赋值运算符,如果变量已经定义则不进行任何操作
- 如果变量未定义,则求值分配
Default_Name = DOG
Default_Name ?= CAT
Final_Name = $(Default_Name)
debug :
echo $(Default_Name) $(Final_Name)
#make debug结果:
echo DOG DOG
DOG DOG
+=
- 累加赋值运算符
- 将运算符右边的值累加到左边变量值后(中间带空格),新值赋给变量
Default_Name := DOG
Final_Name := CAT
Final_Name += $(Default_Name)
debug :
echo $(Final_Name)
#make debug结果:
echo CAT DOG
CAT DOG
\
- 续行符
- 将一行分布于几行
例:
cpp := src/main.cpp
obj := obj/main.o
$(obj) : ${cpp}
g++ -c $< -o $@ # $<表示src/main.cpp;$@表示obj := obj/main.o
函数调用和变量一样,以“$”开头,语法如下:
$(fn argument,argument...) 或者 ${fn argument,argument...}
- fn:函数名;
- arguments:参数,参数间以“,”(逗号)分隔,函数名与参数间用“空格”分开
$(shell <command> <arguments>)
$(shell find src -name *.cpp)
#查找src[目录]中的 *.cpp[所有.cpp后缀的文件]
$(subst <from>, <to>, <text>)
src = src/main.cpp src/src1.cpp src/src2.cpp
obj = $(subst src/,obj/,$(src))
obj = $(subst .cpp,.o,$(src))
debug :
echo $(obj)
#make debug结果:
echo src/main.o src/src1.o src/src2.o
src/main.o src/src1.o src/src2.o
$(patsubst <pattern>, <replacement>, <text>)
src = src/main.cpp src/src1.cpp src/src2.cpp
obj := $(patsubst src/%.cpp,obj/%.o,$(src))
debug :
@echo $(src) #echo前面加@,shell中不打印echo指令
@echo $(obj)
#make debug结果:
src/main.cpp src/src1.cpp src/src2.cpp
obj/main.o obj/src1.o obj/src2.o
$(wildcard <pattern>)
cpp_srcs := $(wildcard src/*.c)
debug :
@echo $(cpp_srcs)
.PHONY : debug
#make debug结果:
src/func2.c src/main.c src/func1.c
$(foreach <var>, <list>, <text>)
lib_path := E:\7.MyProject\UpperSystem\opencv\lib \
C:\libmodbus-3.1.6\lib \
include_path := $(foreach item, $(lib_path), -I$(item))
#伪代码:
#for(item : lib_path) lib_path中的每个成员item
# item := -Iitem 成员前加-I
debug :
@echo $(lib_path)
@echo $(include_path)
#make debug结果:
E:\7.MyProject\UpperSystem\opencv\lib C:\libmodbus-3.1.6\lib
-IE:\7.MyProject\UpperSystem\opencv\lib -IC:\libmodbus-3.1.6\lib
同等效果的另一种简洁写法:
include_path := $(lib_path:%=-I%)
$(dir <names...>)
示例1:
cpp_srcs := $(wildcard src/*.c)
debug :
@echo $(dir $(cpp_srcs))
@echo $(cpp_srcs)
.PHONY : debug compile
#make debug结果:
src/ src/ src/
src/func2.c src/main.c src/func1.c
示例2:
PS E:\8.test_demo\1.makefileTest> ls
目录: E:\8.test_demo\1.makefileTest
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2024/3/14 21:35 .vscode
d----- 2024/3/14 21:42 src
-a---- 2024/3/14 21:43 232 Makefile
cpp_srcs := $(wildcard src/*.c)
cpp_objs := $(patsubst src/%.c,obj/%.o,$(cpp_srcs)) #cpp_objs变量以cpp_srcs为基础,将成员字符串的src/和.c替换为obj/和.o,该阶段仅限于将cpp_objs变量进行字符串赋值
obj/%.o :src/%.cpp_objs
@g++ -c $^ -o $@
compile : $(cpp_objs)
debug :
@echo $(cpp_srcs)
@echo $(cpp_objs)
.PHONY : debug compile
#make debug结果:
PS E:\8.test_demo\1.makefileTest> mingw32-make compile
Assembler messages:
Fatal error: can't create obj/func2.o: No such file or directory
mingw32-make: *** [Makefile:5: obj/func2.o] Error 1
#因为没有存放.o文件的obj目录(文件夹),无法完成编译
cpp_srcs := $(wildcard src/*.c)
cpp_objs := $(patsubst src/%.c,obj/%.o,$(cpp_srcs))
obj/%.o :src/%.c
@mkdir -p $(dir $@) #增加该行可创建目录
#"mkdir":shell指令创建文件夹,(Linux或Unix平台,Windows平台不支持),
#$(dir $@):使用dir函数提取目标文件名称中的目录名,
#$@:表示所有目标文件
@g++ -c $^ -o $@
compile : $(cpp_objs)
debug :
@echo $(cpp_srcs)
@echo $(cpp_objs)
.PHONY : debug compile
$(notdir <names...>)
例:
cpp_srcs := $(wildcard src/*.c) #检索./src文件夹中的所有文件名
debug :
@echo $(cpp_srcs)
@echo $(notdir $(cpp_srcs)) #将取出目录名的文件名提取出来
.PHONY : debug
#make debug结果:
src/func2.c src/main.c src/func1.c
func2.c main.c func1.c
$(filter <pattern...>,<text>)
例:
srcs := $(wildcard src/*) #检索./src文件夹中的所有文件名
cpp_srcs := $(filter %.c %.cpp, $(srcs)) #将srcs中所有.c和.cpp格式的文件筛选出来
debug :
@echo $(srcs)
@echo --------------------------------------------------------------------------------------------------------
@echo $(cpp_srcs)
.PHONY : debug
#make debug结果:
src/modbus-tcp.c src/modbus-rtu.c src/modbus-tcp.h src/modbus-rtu.h src/modbus.c src/master_demo.cpp src/modbus-data.c src/modbus-tcp-private.h src/modbus-rtu-private.h src/modbus.h src/config.h src/modbus-version.h src/modbus-private.h src/slave_demo.cpp
--------------------------------------------------------------------------------------------------------
src/modbus-tcp.c src/modbus-rtu.c src/modbus.c src/master_demo.cpp src/modbus-data.c src/slave_demo.cpp
$(filter-out <pattern...>,<text>)
例:
srcs := $(wildcard src/*) #检索./src文件夹中的所有文件名
other_srcs := $(filter-out %.c %.cpp, $(srcs)) #将srcs中不是.c和.cpp格式的文件筛选出来
debug :
@echo $(srcs)
@echo --------------------------------------------------------------------------------------------------------
@echo $(other_srcs)
.PHONY : debug
#make debug结果:
src/modbus-tcp.c src/modbus-rtu.c src/modbus-tcp.h src/modbus-rtu.h src/modbus.c src/master_demo.cpp src/modbus-data.c src/modbus-tcp-private.h src/modbus-rtu-private.h src/modbus.h src/config.h src/modbus-version.h src/modbus-private.h src/slave_demo.cpp
--------------------------------------------------------------------------------------------------------
src/modbus-tcp.h src/modbus-rtu.h src/modbus-tcp-private.h src/modbus-rtu-private.h src/modbus.h src/config.h src/modbus-version.h src/modbus-private.h
$(basename <names...>)
例:
srcs := $(wildcard src/*.cpp)
debug :
@echo $(srcs)
@echo --------------------------------------------------------------------------------------------------------
@echo $(basename $(srcs))
.PHONY : debug
#make debug结果:
src/master_demo.cpp src/slave_demo.cpp
--------------------------------------------------------------------------------------------------------
src/master_demo src/slave_demo
$(addsuffix <suffix>,<name>)
例:
$(addsuffix .c,main foo)
#make debug结果:
main.c foo.c
$(addprefix <prefix>,<name>)
例:
$(addprefix src/,main.o foo.o)
#make debug结果:
src/main.c src/foo.c
#测试demo文件结构
PS E:\8.test_demo\1.makefileTest> ls
目录: E:\8.test_demo\1.makefileTest
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2024/3/14 21:35 .vscode
d----- 2024/3/16 17:44 bin
d----- 2024/3/16 17:44 obj
d----- 2024/3/14 21:42 src
-a---- 2024/3/16 17:36 247 Makefile
PS E:\8.test_demo\1.makefileTest> cd src
PS E:\8.test_demo\1.makefileTest\src> ls
目录: E:\8.test_demo\1.makefileTest\src
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2024/3/14 22:22 58 func1.c
-a---- 2024/3/14 22:22 59 func2.c
-a---- 2024/3/16 11:54 127 main.c
Makefile示例1:一个最简单直白的Makefile文件
bin/exec : obj/main.o obj/func1.o obj/func2.o
@g++ obj/main.o obj/func1.o obj/func2.o -o bin/exec
obj/main.o : src/main.c
@g++ -c src/main.c -o obj/main.o
obj/func1.o : src/func1.c
@g++ -c src/func1.c -o obj/func1.o
obj/func2.o : src/func2.c
@g++ -c src/func2.c -o obj/func2.o
clean :
@del /q bin obj
.PHONY : clean
Makefile示例2:一个使用变量、预定义变量和函数的Makefile文件
cpp_srcs := $(wildcard src/*.c)
cpp_objs := $(patsubst src/%.c,obj/%.o,$(cpp_srcs))
obj/%.o : src/%.c
@g++ -c $^ -o $@
bin/exec : $(cpp_objs)
@g++ $^ -o $@
compile : bin/exec
clean :
@del /q bin obj
.PHONY : compile clean
很明显,当工程文件更多时示例2更易于维护,特别是源文件有增减时。
该篇为第一部分,第二部分请移步学习笔记——Makefile基础及入门2