【Linux】 Makefile 详解

Makefile

文章目录

1.初次实现

1.1 没创建之前

cpp 复制代码
mkdir lesson9
cd lesson9
vim myproc.c     # 写入上面 代码,:wq 保存退出

进 vim 后写(i 进插入模式,写完 ESC 再 :wq 保存退出):

cpp 复制代码
#include <stdio.h>

int main()
{
    printf("hello bit!\n");
    return 0;
}

这时候只要一串"gcc -o myproc myproc.c"就能让它进行编译

然后再来这一行"./myproc"就能够直接运行

可是感觉这样有点麻烦,所以我们实现Makefile




1.2使用makefile




Makefile的格式

cpp 复制代码
目标文件: 依赖文件列表
<Tab>依赖方法  #前面必须是tab



创建第一个makefile

  • 创建:
bash 复制代码
vim Makefile  # 新建并打开 Makefile
  • 写入
makefile 复制代码
myproc:myproc.c
    gcc -o myproc myproc.c
  • 退出写入之后运行
bash 复制代码
make
gcc -o myproc myproc.c
ls
Makefile  myproc  myproc.c





实现clean

  • 重新打开 Makefile
bash 复制代码
vim Makefile  # 重新打开 Makefile,接着往下写
  • 写入Makefile
makefile 复制代码
myproc:myproc.c
    gcc -o myproc myproc.c

.PHONY:clean  # 修饰符,声明 clean 是伪目标
clean:  # clean 也要有依赖关系,但依赖文件列表可以为空
    rm -f myproc  # 依赖方法:删掉编译产物
  • 流程
bash 复制代码
make        # 自动执行gcc,编译生成myproc
gcc -o myproc myproc.c
make clean  # 执行rm,删掉myproc  
rm -f myproc
ls
Makefile  myproc.c 
clean的语法

一般Makefile的格式

cpp 复制代码
目标文件: 依赖文件列表
<Tab>依赖方法  #前面必须是tab
点 说明
clean 的语法结构 和 myproc 一模一样:也是一组"依赖关系 + 依赖方法"。区别只是它的方法不形成文件,而是删文件
依赖列表为空 clean: 后面什么都不写也合法 ------ 它不依赖任何文件
为什么 make clean 要带个名字 make 默认只做第一个目标(见块 4),clean 是第二个,得显式点名








1.3 把make和clean调个位置------ 验证「默认目标」规则

bash 复制代码
vim Makefile

调换后的 Makefile:

makefile 复制代码
.PHONY:clean
clean:
    rm -f myproc

myproc:myproc.c
    gcc -o myproc myproc.c
  • 展示:
bash 复制代码
make
rm -f myproc
make myproc
gcc -o myproc myproc.c 





点 说明
默认目标 make 从上往下 扫描 makefile,把第一个目标 当默认目标(.PHONY 这种以点开头的特殊目标会被跳过)
只做第一个 第一个目标做完了,第二个不会 跟着做,除非你显式点名(make 目标名)
结论 所以默认把可执行程序写在最前面。










1.4 .PHONY ------让目标总是被执行






尝试多敲几次make

bash 复制代码
# 先清理干净,再连续敲四次 make,一次都别落
make clean
rm -f myproc
make
gcc -o myproc myproc.c
make
make: 'myproc' is up to date.
make
make: 'myproc' is up to date.
make
make: 'myproc' is up to date.



关键点
点 说明
现象 第 1 次真编;第 2 ~ 4 次只回一句 make: 'myproc' is up to date.
up to date 什么意思 make 认为目标文件已经是最新的,没必要重新生成










给 myproc 也加上 .PHONY





打开 Makefile,在 myproc: 那一组的上面 加一行修饰符,然后 make 四次:

bash 复制代码
vim Makefile
.PHONY:myproc  # 加这一行:修饰下面的目标
myproc:myproc.c
    gcc -o myproc myproc.c

.PHONY:clean
clean:
    rm -f myproc
make
gcc -o myproc myproc.c
make
gcc -o myproc myproc.c
make
gcc -o myproc myproc.c
make
gcc -o myproc myproc.c



关键点

问题 答案
结论 .PHONY = 让 make 忽略源文件和可执行目标的 M 时间对比,从而让目标总是被执行
.PHONY 是什么 makefile 里的一种修饰符 ,被它修饰的目标叫伪目标
作用 目标总是被执行 ------ 也就是对应的依赖方法每次都跑
本质是什么 make 调用 gcc 时忽略新旧时间对比
不给可执行程序加 一旦加了,就再也享受不到"源文件没改就不重编"的好处,多文件大项目会把时间全浪费在重复编译上
给 clean 加 清理每次都应该做彻底,后面清理可能做很多事,要保证它纯粹、干净,不会留下奇奇怪怪的中间产物





有关ACM时间

缩写 全称 含义 触发场景
A Access 访问时间 文件内容被读取的时间 cat、vim 打开、程序读文件,只看内容不修改
M Modify 修改时间 文件内容数据被改动的时间 echo、vim 保存写入,文件里面文字变了
C Change 状态变更时间 文件属性 / 元信息被改动的时间 chmod 改权限、chown 改所有者、mv 改名、硬链接增减(inode 属性变,文件内容不变)



查看时间状态的指令:

bash 复制代码
stat myproc.c  # 看源文件的三条时间 



bash 复制代码
stat myproc.c  # 看源文件的三条时间
  File: 'myproc.c'
  Size: 359              Blocks: 8          IO Block: 4096   regular file
Device: fd01h/64769d     Inode: 1321125      Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1000/     whb)   Gid: ( 1000/     whb)
Access: 2024-10-23 18:51:20.000000000 +0800
Modify: 2024-10-23 18:51:20.000000000 +0800
Change: 2024-10-23 18:51:20.000000000 +0800

stat myproc  # 看可执行文件的三条时间
  File: 'myproc'
  Size: 8440             Blocks: 24         IO Block: 4096   regular file
Device: fd01h/64769d     Inode: 1321126      Links: 1
Access: (0775/-rwxrwxr-x)  Uid: ( 1000/     whb)   Gid: ( 1000/     whb)
Access: 2024-10-23 19:04:18.051190859 +0800
Modify: 2024-10-23 19:04:18.051190859 +0800
Change: 2024-10-23 19:04:18.051190859 +0800





有关touch改时间
  • make识别重复的原理:通过modify时间
  • touch 已存在文件,会改时间,touch完了之后又能make
用法 效果
touch 不存在的文件 新建一个空文件
touch 已存在的文件 把它的三条时间全部统一更新为现在(核心作用:change file timestamp)
touch -a 文件 只改 access 时间
touch -m 文件 只改 modify 时间
touch -c 文件 不常用 ------ M 时间一改,属性一般也跟着改了










2.第二版Makefile

之前的Makefile(是一个简化版的版本):

makefile 复制代码
myproc:myproc.c
    gcc -o myproc myproc.c



详细版的是这样的:

makefile 复制代码
myproc: myproc.o	# 最终目标依赖目标文件
	gcc myproc.o -o myproc	# 链接:把 .o 变成可执行

myproc.o: myproc.s	# .o 依赖汇编文件
	gcc -c myproc.s -o myproc.o	# 汇编:把 .s 变成 .o

myproc.s: myproc.i	# .s 依赖预处理文件
	gcc -S myproc.i -o myproc.s	# 编译:把 .i 变成 .s

myproc.i: myproc.c	# .i 依赖源文件
	gcc -E myproc.c -o myproc.i	# 预处理:把 .c 变成 .i

.PHONY:clean
clean:
	rm -f *.i *.s *.o myproc





gcc 对照表

阶段 命令 输入 → 输出 干什么
预处理 gcc -E myproc.c -o myproc.i .c → .i 宏替换、去注释、条件编译、头文件展开
编译 gcc -S myproc.i -o myproc.s .i → .s 检查语法,翻译成汇编
汇编 gcc -c myproc.s -o myproc.o .s → .o 汇编代码 → 二进制目标文件
链接 gcc myproc.o -o myproc .o → 可执行 和标准 C 库(libc.so.6)链接










3.第三版Makefile

3.1定义变量

先vim进文件里

然后在前面添加:

复制代码
BIN=proc.exe
CC=gcc
SRC=myproc.c
FLAGS=-o
RM=rm -f

对应关系:

写法 意思
BIN=proc.exe 以后用 $(BIN),就替换成 proc.exe
CC=gcc 以后用 $(CC),就替换成 gcc
SRC=myproc.c 以后用 $(SRC),就替换成 myproc.c
FLAGS=-o 以后用 $(FLAGS),就替换成 -o
RM=rm -f 以后用 $(RM),就替换成 rm -f



cpp 复制代码
proc.exe : myproc.c
   ↑           ↑
  $@          $^
 目标        依赖文件





3.2新的Makefile总代码

makefile 复制代码
BIN=proc.exe  # 目标文件名
CC=gcc  # 编译器
SRC=myproc.c  # 源文件
FLAGS=-o  # 编译选项
RM=rm -f  # 删除命令

$(BIN):$(SRC)  # 换成变量引用:proc.exe:myproc.c
    @$(CC) $(FLAGS) $@ $^  # @gcc -o proc.exe myproc.c
    @echo "linking ... $^ to $@"  # @echo "linking ... myproc.c to proc.exe"
.PHONY:
clean:
    @$(RM) $(BIN) # @rm -f proc.exe
    @echo "remove ... $(BIN)" # @echo "remove ... proc.exe"

.PHONY:test
test:
    @echo $(BIN)
    @echo $(CC)
    @echo $(SRC)
    @echo $(FLAGS)
    @echo $(RM)

代入举例:

  • 图中的代码:

    (BIN):(SRC)
    @(CC) (FLAGS) @ ^

  • 相当于:

    proc.exe:myproc.c
    @gcc -o proc.exe myproc.c











4.最终版Makefile

目标只有一个:不管目录里有几个 .c,makefile 一行都不用改。

两个动作:

  • ① 让 makefile 自动列出当前目录的源文件;
  • ② 用模式规则 %.o:%.c 一句话搞定"所有 .c → 同名 .o"。
makefile 复制代码
BIN=proc.exe
CC=gcc
# 自动列出当前目录所有 .c(写法二选一,一种注释掉)
#SRC=$(shell ls *.c)
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)  # 把 SRC 里的每个 .c 换成 .o
LFLAGS=-o  # 链接选项
FLAGS=-c  # 编译选项
RM=rm -f

$(BIN):$(OBJ)
    @$(CC) $(LFLAGS) $@ $^
    @echo "linking ... $^ to $@"
%.o:%.c  # 模式规则:所有 .c 展开成同名 .o
    @$(CC) $(FLAGS) $<
    @echo "compling ... $< to $@"
.PHONY:clean
clean:
    $(RM) $(OBJ) $(BIN)

.PHONY:test
test:
    @echo $(SRC)
    @echo $(OBJ)





关键点




语法 含义(老师板书原批注)
$(wildcard *.c) makefile 内置函数 ,默认把当前目录所有 .c 通配出来。板书批注:"显示当前目录下所有的 .c 文件名"
$(shell ls *.c) 另一种写法:makefile 可以执行 shell 命令 。扫描到这行就执行 ls *.c,把结果存进 SRC。两种方式自己选
OBJ=$(SRC:.c=.o) 替换 语法:把 SRC 内部每个"文件名 .c"换成"文件名 .o"。板书批注:"SRC 内部的文件名 .c → 文件名 .o"
%.o:%.c 模式规则 。% 是通配符,板书批注:"把当前路径下所有的 .o/.c 依次展开 "。目录里有 100 个 .c 就自动展开成 100 组依赖关系
$< 表示当前正在处理的那一个 .c 文件(一条一条交给 gcc)。课件里把 $< 误写成 %<,以老师课上讲的 $< 为准
@ 不回显命令,实现静默编译和静默清理





特殊符号速查 含义
$@ 目标文件
$^ 所有依赖文件
$< 第一个依赖文件(模式规则里就是"当前那个 .c")
@ 命令前加它 → 不回显这条命令










相关推荐
天疆说2 小时前
腾讯会议 Linux 版在 NVIDIA 显卡上看不到画面:EGL 像素配置与窗口视觉不匹配的排查与修复
linux·数据库·腾讯会议
(Charon)2 小时前
【C++面试】迭代器失效是什么?vector、list、map中如何避免迭代器失效
c++·算法·面试
geats人山人海2 小时前
linux 2 基本操作与vim
linux·运维·vim
常州晟凯电子科技2 小时前
sigmaStar SSC305DE/SSC308开发笔记之开发环境搭建和SDK编译
人工智能·笔记·嵌入式硬件·物联网·性能优化·视频编解码
怕浪猫2 小时前
面试官让我手写一个 Tool Use,我用了 3 种方案
设计模式·面试·程序员
wdfk_prog3 小时前
LWIP教程 00:从网线上的电信号到 lwIP——PHY、MAC、DMA 与 `netif` 的完整边界
运维·网络·笔记·学习·ip
小黄蚁3 小时前
无人驾驶入门笔记(一)
笔记·自动驾驶·信息与通信
lisanmengmeng3 小时前
NRPE 添加命令(三)
java·linux·服务器
人工智能培训3 小时前
数字孪生驱动大模型工业知识库:为具身机器人植入领域专业经验
大数据·linux·服务器·前端·人工智能