1、C/C++的编译指令编译指令
本文为Makefile的学习笔记,学习的素材主要为Bilibili UP主李呵欠的“GUN Makefile编译C/C++教程”。笔记中实验的平台为Window+VScode,实验的实现和李呵欠UP主的视频素材有所差别。
1.1、C语言的gcc编译指令
源文件.c -> 预编译文件.i -> 汇编语言.s -> 目标文件.o -> 可执行文件(自定义名称,无需后缀)
1.1.1预处理
gcc -E[.c源文件](可选选项: -o [自定义文件名])
例:
gcc -E main.c #-E告诉编译器只进行预处理工作,默认不生成“.i”文件;
gcc -E main.c -o objectFile.i #-o objectFile.i告诉编译器预处理后生成“objectFile.i”文件
1.1.2源文件到汇编文件
gcc -S[.c源文件](可选选项: -o [自定义文件名])
例:
gcc -S main.c #-S告诉编译器进行预处理和编译成汇编语言,默认生成“main.s”文件;
gcc -S main.c -o objectFile.s #-o objectFile.s告诉编译器生成的汇编文件命名为“objectFile.s”
1.1.3源文件到目标文件
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文件
1.1.4源文件到可执行文件
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
1.1.5静态库创建和链接
第一步:将源文件编译成.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
1.1.6动态库创建和链接
第一步:将源文件编译成.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
1.2、C++的g++编译指令
向下支持C源文件编译,与C语言的gcc指令基本一样,将gcc改为g++。
1.2.1预处理
g++ -E[.c源文件/.cpp源文件](可选选项: -o [自定义文件名])
例:
g++ -E main.cpp #-E告诉编译器只进行预处理工作,默认不生成“.i”文件;
g++ -E main.cpp -o objectFile.i #-o objectFile.i告诉编译器预处理后生成“objectFile.i”文件
1.2.2源文件到汇编文件
g++ -S[.c源文件/.cpp源文件](可选选项: -o [自定义文件名])
例:
g++ -S main.c #-S告诉编译器进行预处理和编译成汇编语言,默认生成“main.s”文件;
g++ -S main.cpp -o objectFile.s #-o objectFile.s告诉编译器生成的汇编文件命名为“objectFile.s”
1.2.3源文件到目标文件
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文件
1.2.4源文件到可执行文件
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
1.2.5静态库创建和链接
第一步:将源文件编译成.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
1.2.6动态库创建和链接
//第一步:将源文件编译成.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
2、makefile语法
2.1、基本语法
目标文件 : 依赖文件(单个或多个)
[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
2.2 伪目标
伪目标不是目标文件,它是一个标签,make时要显式地指明才能生效标签后的指令,
如:make debug。
伪目标不能与文件重名,一般为了避免重名用“.PHONY”来指明伪目标,
如:.PHONY : debug
2.3 变量
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
2.4 常用符号
2.4.1 “=”赋值运算符
=
- 赋值运算符,右边的值分配给左边的变量
- 使用该运算符赋值的变量,一旦右边的值在下文发生了更新,被赋值变量的值随之改变
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.
2.4.2 “:=”立即赋值运算符
:=
- 立即赋值运算符,用于定义变量是立即求值
- 该值一旦定义不再改变,即使右值在下文中发生了改变
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
2.4.3 “?=”默认赋值运算符
?=
- 默认赋值运算符,如果变量已经定义则不进行任何操作
- 如果变量未定义,则求值分配
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
\
- 续行符
- 将一行分布于几行
2.5 预定义变量
- $@:目标的完整名称;
- $<:依赖文件中第一个依赖文件的名称;
- $^:所有的依赖文件,以空格分开,不包含重复的依赖文件;
例:
cpp := src/main.cpp
obj := obj/main.o
$(obj) : ${cpp}
g++ -c $< -o $@ # $<表示src/main.cpp;$@表示obj := obj/main.o
2.6 常用函数
2.6.1 基本语法
函数调用和变量一样,以“$”开头,语法如下:
$(fn argument,argument...) 或者 ${fn argument,argument...}
- fn:函数名;
- arguments:参数,参数间以“,”(逗号)分隔,函数名与参数间用“空格”分开
2.6.2 shell函数(Linux平台,Windows平台指令不兼容和无启动进程的权限)
$(shell <command> <arguments>)
- 名称:shell
- 功能:调用shell命令,常用shell命令
- 返回:shell命令的执行结果
例:
$(shell find src -name *.cpp)
#查找src[目录]中的 *.cpp[所有.cpp后缀的文件]
2.6.3 subst函数
$(subst <from>, <to>, <text>)
- 名称:subst——字符串替换函数
- 功能:把字符串“text”中的“from”字串替换为“to”字串
- 返回:返回替换完成后的字符串
例:
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
2.6.4 patsubst函数
$(patsubst <pattern>, <replacement>, <text>)
- 名称:patsubst——模式字符串替换函数
- 功能:将字符串“text”,由“pattern”格式替换为“replacement”格式,通配符%表示任意长度的字串。
- 返回:返回替换完成后的字符串
例:
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
2.6.5 wildcard函数
$(wildcard <pattern>)
- 功能:按照“pattern”路径和文件格式进行文件检索
- 返回:返回符合结果的文件名
cpp_srcs := $(wildcard src/*.c)
debug :
@echo $(cpp_srcs)
.PHONY : debug
#make debug结果:
src/func2.c src/main.c src/func1.c
2.6.6 foreach函数
$(foreach <var>, <list>, <text>)
- 名称:循环函数
- 功能:把字符串“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%)
2.6.7 dir函数
$(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
2.6.8 notdir函数
$(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
2.6.9 filter函数
$(filter <pattern...>,<text>)
- 功能:字符串处理,将“text”中成员(每个成员以空格为分割)匹配“pattern”格式的筛选出来。
- 返回:返回筛选出的字串。
例:
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
2.6.10 filter-out函数
$(filter-out <pattern...>,<text>)
- 功能:字符串处理,将“text”中成员(每个成员以空格为分割)不匹配“pattern”格式的筛选出来。
- 返回:返回筛选出的字串。
例:
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
2.6.11 basename函数
$(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
2.6.12 addsuffix函数
$(addsuffix <suffix>,<name>)
- 功能:文件名处理,basename的逆反,给无后缀的文件名“name”,添加后缀“suffix”。
- 返回:返回添加后缀的文件名。
例:
$(addsuffix .c,main foo)
#make debug结果:
main.c foo.c
2.6.13 addprefix函数
$(addprefix <prefix>,<name>)
- 功能:文件名处理,给无目录的文件名“name”,添加目录前缀“prefix”。
- 返回:返回添加前缀的文件名。
例:
$(addprefix src/,main.o foo.o)
#make debug结果:
src/main.c src/foo.c
3 简单的makefile示例(Windows平台)
#测试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