Make和Makefile学习笔记
make和makefile
为什么需要make和makefile?
对于大型程序来说,多文件之间具有耦合和关联性,如果其中部分文件更改后,关联文件没有重新编译,就会导致程序出错的潜在问题,通过makefile可以执行make规则,make就可以自动执行gcc指令。
程序的编译过程主要分为编译和链接:
编译时,主要是要告诉编辑器头文件的所在位置,当文件中语法全部正确时,编译器就可以自动编译出中间目标文件(.o或者.obj)编译器只检查程序语法,以及函数、变量的声明。
链接时,主要是链接函数和全局变量,链接器器不管函数所在的源文件,只管函数的中间目标文件,当中间目标文件过多时,就需要给中间目标文件打包提高编译效率,打包好的文件就叫库文件(.lib或者.a)链接器会在所有的目标文件中找寻函数的实现。\
GNU make的基本规则:
target ... : prerequisites ... (1)
command (2)
...
...
在makefile中,注释用#开始。
1.依赖关系(前置条件)
依赖关系定义最终应用程序中的每个文件于源文件之间的关系,就是上文GNU make规则中(1)处。写法如下,前面为目标名称,后面紧跟一个冒号,接着是用空格或者制表符隔开的多个文件依赖列表:
myapp: main.o 2.o 3.0
main.0: main.c a.h
在makefile中,定义目标all可同时制作多个文件,若未定义,则make只创建在makefile中找到的第一个目标!
除了文件名当目标外,还可以是一些伪命令,比如clean:,这样在执行make clean指令后,就会自动执行后面的shell指令。
all: myapp myapp.l
依赖关系有什么用?它指定了文件之间的依赖关系,当开始编译时,如果有依赖文件发生改变(时间戳比其他的新),编译器就会自动寻找其关联的文件,并进行编译,保证全局代码的最新。
如果只有文件名,没有依赖关系的情况下,则表示每次都执行该目标名称后面的指令。
2.创建规则
在上述GNU make规则中(2)就是在生成目标文件后可以执行的shell命令,在设定依赖编译器只知道依赖关系,makefile还需要知道如何创建文件。因此我们可以自动创建规则,大多数规则都包含一个简单命令,在每一个命令前有且只能有一个制表符。
myapp:main.o 2.o 3.o
gcc -o myapp main.o 2.o 3.o
在这些编译命令中,主要是使用对应的编译链工具(如gcc),并且通过指定参数完成编译。
PS:【1】每一行命令都在单独的shell中执行,指令之间没有继承关系。如果需要继承关系,但是又太长,可以使用反斜杠转义 \ 。
【2】 在正常执行的情况下,make会打印每一条指令,然后再执行,叫做回升(echoing)在每条命令前加上@,就可以关闭回声(输出)其实就是编译的时候会出现的一大堆的字的来源。
test:
@echo $$HOME
在makefile中还有一些自动变量:
$@ ——目标文件(shell中:输出用[“ ”]括起来的情况,单独输出)
$^ ——所有的依赖文件
$< ——第一个依赖文件
$? ——指代所有依赖文件中的最新的那个(shell中:最后运行的命令结束代码(返回值))
$ ——指代通配符%的匹配部分(shell中:所有的参数列表)*
以上这些是makefile中用来简化创建规则的,第一个是直接指代上面依赖关系中的目标文件,第二个是指代所有的依赖文件,第三个是指定第一个依赖文件,可以减少重复书写文件名的麻烦。
其他的(shell指令中的):
$$ ——shell本身的PID(ProcessID)
$! ——shell最后运行的后台PID
$- ——使用Set命令设置的Flag一览
$1~$n ——添加到Shell的各参数值
dest/%.txt: src/%.txt
@[ -d dest ] || mkdir dest
cp $< $@
在makefile中还有一些判断和循环的语句,遵循Bash语法:
ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif=
甚至还可以使用函数,目前还没用过:
$(function arguments)
3.宏
makefile中的宏,类似于C语言中的#define,定义和引用方式如下所示,一般写在makefile的开头,用于方便替换一些常用的变量。
MACRONAME=value
$(MACRONAME)
${MACRONAME}
在宏定义内容过长时,可以用\进行换行。
在makefile中,“=” 还有一些特殊的赋值语句:
“=” ——赋值的值为makefile中最后被指定的值,会将整个makefile展开,拉通决定变量的值
“:=” ——直接赋值,直接将当前等式右边的值赋给左边。
“?=” ——如果该变量没有被赋值,则赋予等号后面的值。
“+=” ——将等号后面的值加到前面的变量上。
在make中,还提供了一系列内置变量,$(CC)指向当前使用的编译器等。
4.内置规则
make本身带有大量的内置规则,就是说执行make+可执行文件名 后,make会自动匹配该文件并编译。并且我们可以只在makefile中指定依赖关系即可。通过make -p可以打印出make指令的内置规则,通过make -MM指令可以导出项目中的依赖关系清单。
5.多目标
通过all:指定,同时生成多个目标。
通过install:指定,让可执行文件直接安装。
通过clean:指定清理临时文件。
6.Linux内核中的makefile
linux内核也采用makefile文件对内核进行编译,并且可以通过.config文件直接生成makefile。.config文件可以通过menuconfig等方式进行配置。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)