学习笔记——Makefile基础及入门1

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

  • 20
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZW_finder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值