在这里插入图片描述

在这里插入图片描述

🔥@雾忱星: 个人主页
👀专栏:《C++学习之旅》《Linux学习指南》
💪学习阶段:C/C++、Linux
⏳“人理解迭代,神理解递归。



引言

手动gcc编译适合单文件测试,面对大型多文件项目时,操作繁琐、易出错且效率极低。
make工具搭配Makefile文件,能实现工程自动化编译构建,省去重复指令,是大型项目开发的必备技能。本篇将从基础认知、实操案例到进阶语法,拆解make与Makefile的用法,快速掌握自动化编译核心。


一、认知:make和M/makefile

🌌解决痛点
在前面我们学习了 gcc 编译命令,利用不同选项可以将源文件编译成不同后缀的文件,比如:.i/.o...。但是遇到大型项目进行编译文件,手动输入编译命令就有点不够看了,一不小心还可能将源文件覆盖,造成重大损失。哎~ 下面就介绍解决痛点的方法。

🔍 【make】:

make是一款自动化构建工具,能够解释makefile文件中预先设置好的指令,自动判断文件的编译情况。其本身就是一个解释工具,遵循“解析规则、执行命令”。

🔍 【makefile】:

makefile是一个文本文件,用于定义目标文件、依赖文件列表、依赖方法等要素。其定义一系列的规则,决定了一个工程中众多源文件哪些需要先编译,哪些后编译。源于它的便捷性,有人以“会不会写 makefile 来定义一个人是否剧本完成大型工程的能力”。

🗝️ 【关系】:
运行时,make 会在当前目录寻找 M/makefile 文件,解析其中规则,执行对应操作; makefile 写好规则,依赖 make 就形成了自动化编译


二、实操:手写简单 Makefile 入门

test.c进行自动化编译,在同一目录下创建makefile文件。

[tac@VM-0-6-centos lesson11]$ touch test.c
[tac@VM-0-6-centos lesson11]$ ll
total 0
-rw-rw-r-- 1 tac tac 0 Jan 31 14:44 test.c
[tac@VM-0-6-centos lesson11]$ vim test.c 
[tac@VM-0-6-centos lesson11]$ touch makefile
[tac@VM-0-6-centos lesson11]$ vim makefile 

源文件内容:

 #include <stdio.h>
  2 
  3 int main()
  4 {
  5     printf("hello 雾忱星");                                                                                                                    
  6     return 0;
  7 }

makefile 文件内容:

  1 test:test.c
  2     gcc test.c -o test
  3 .PHONY:clean
  4 clean:
  5     rm -f test    

执行效果:

[tac@VM-0-6-centos lesson11]$ make
gcc test.c -o test
[tac@VM-0-6-centos lesson11]$ ll
total 20
-rw-rw-r-- 1 tac tac   64 Jan 31 14:51 makefile
-rwxrwxr-x 1 tac tac 8360 Jan 31 14:51 test
-rw-rw-r-- 1 tac tac   80 Jan 31 14:45 test.c
[tac@VM-0-6-centos lesson11]$ ./test 
hello 雾忱星[tac@VM-0-6-centos lesson11]$ 
[tac@VM-0-6-centos lesson11]$ make clean
rm -f test
[tac@VM-0-6-centos lesson11]$ ll
total 8
-rw-rw-r-- 1 tac tac 64 Jan 31 14:51 makefile
-rw-rw-r-- 1 tac tac 80 Jan 31 14:45 test.c

三、基础解析:makefile 规则

3.1 Makefile 核心语法规则

目标文件: 依赖文件列表
    依赖方法1
    依赖方法2
    ...
  • 目标文件: 想要得到什么文件(可执行文件test);
  • 依赖文件: 想得到的文件依赖什么文件实现(由test.c转换为test),可以为空;
  • 依赖关系: 目标文件和依赖文件;
  • 依赖方法: 得到想得到的文件的具体方法,可以多个方法;

注意: 依赖方法必须 Tab键开头,若空格代替,运行 make 会报错;添加注释以 #开头。

在这里插入图片描述

3.2 make 的默认执行与指定目标行为

📖 【默认行为】:
执行make指令,默认生成从上向下遇到的第一个目标文件的依赖方法。看图片,第一个目标文件是tets,就默认为gcc test.c -o test。当然,将第二个目标文件clean移到前面,make就会默认为clean

📖 【其他行为】:
对于第二个、第三个……目标文件,就需要指明目标文件make 目标文件名来执行。
在这里插入图片描述

3.3 项目闭环:程序清理(伪目标)

  一个完整的工程是有清理环节的(避免冗余文件堆积),那么目标文件clean就是执行该操作的。值得注意的是: 清理环节没有依赖列表文件,但是仍可以执行依赖方法。一般将clean这种目标文件设置成伪目标,关键词.PHONY修饰。

【伪目标语法】: .PHONY 伪目标

【伪目标特性】: 修饰的目标文件对应的依赖方法总是被执行。

【伪目标本质】: 伪目标不是一个真实的文件,而是一个“标记”,告诉 make 工具“无论是否存在同名文件,都要执行对应的命令”。

📚结论:
.PHONY:让 make忽略源文件和可执行文件的M时间对比。

【时间轴】:新旧时间

📌先说结论

make 判断源文件是否能重新编译,看源文件与可执行文件的M时间的新旧——最新编译,可执行文件的时间是最新的。一定是先有源文件再有可执行文件。

在这里插入图片描述
文件 = 文件内容 + 属性

  • Modify:内容变更,时间更新;
  • Change:属性变更,时间更新;
  • Access:常指文件最近一次被访问的时间。在Linux 早期版本,每次访问都会导致时间更新,大门时会造成大量的IO操作,现今看系统版本。

四、理解核心:依赖关系的推导

将文件编译的过程分阶段进行。

test:test.o
     gcc test.o -o test
 test.o:test.s
     gcc -c test.s -o test.o
 test.s:test.i
     gcc -S test.i -o test.s
 test.i:test.c
     gcc -E test.c -o test.i
 .PHONY:clean
 clean:
     rm -f test.i test.s test.o test    

📌先说结论:

依赖关系的推导:本质就是 make 维护一个栈结构。

在这里插入图片描述

当然,实际中不会这么分布展开编译,最佳实践:

#目标文件:可执行文件,依赖.o文件
 test:test.o
     gcc test.o -o test

#目标文件:.o文件,依赖.c文件
 test.o:test.c
     gcc -c test.c

#清除程序
 .PHONY:clean
 clean:
     rm -f test.o test     

五、进阶解析:makefile 符号化

5.1 自定义、自动变量

主要是将频繁出现的内容以及后续能够替换的内容进行变量化,便于管理。比如:目标文件、依赖文件、编译链接选项等。

🔍 【自定义变量】:

语法 变量名=目标内容使用 $(变量名)

🔍 【自动变量】:

$@:表示当前依赖关系中的目标文件(test);
$^:表示当前依赖关系的依赖文件列表(test.o...
$<:表示将一系列的文件展开,逐个的进行指令(test.c->test.o,test1.c->test1.o...);
%.c/%.o:表示将当前目录下的所有.c/.o文件在此展开;

 30	#自定义变量
 31 Bin=test    #可执行二进制文件
 32 Src=test.c  #源文件
 33 Obj=test.o  #.o文件
 34 
 35 Rm=rm -f    #清除程序
 36 LD_FLAGS=-o #编译链接选项
 37 Flags=-c -Wall
 38 
 39 cc=gcc
 40 Echo=echo
 41 
 42 $(Bin):$(Obj)
 43     @$(Echo) "我要开始链接了...$(Obj) -> $(Bin)"
 44     @$(cc) $^ $(LD_FLAGS) $@
 45 %.o:%.c
 46     @$(Echo) "我要开始编译了...$< -> $@"
 47     @$(cc) $(Flags) $<
 48 
 49 .PHONY:clean
 50 clean:
 51     @$(Rm) $(Obj) $(Bin)
 52     
 53 .PHONY:debug
 54 debug:
 55     @$(Echo) "Bin:$(Bin)"
 56     @$(Echo) "Obj:$(Obj)"
 57     @$(Echo) "Src:$(Src)"               

5.2 内置函数批量处理

主要解决当项目有多个.c文件以及.o文件的情况,无需在逐个定义变量。

  • wildcard;匹配指定格式的文件
#批量获取当前目录下的多个源文件
Src = $(wildcard *.c)
#或者:Src = $(shell ls *.c)-->shell命令获取文件
  • 替换文件后缀
#将Src记录的源文件的后缀替换为.o
Obj = $(Src:.c=.o )

5.3 工程化:多文件项目的通用编写

当项目存在多个文件时,最终版的makefile可以自动适配进行编译。

 30 Bin=test							#可执行二进制文件
 31                                                                                                                         
 33 Src=$(wildcard *.	c)  				#批量获取
 34 
 36 Obj=$(Src:.c=.o)					#批量替换后缀
 37 
 38 Rm=rm -f    						#清除程序
 39 LD_FLAGS=-o
 40 Flags=-c -Wall
 41 
 42 cc=gcc
 43 Echo=echo
 44 
 45 $(Bin):$(Obj)
 46     @$(Echo) "我要开始链接了...$(Obj) -> $(Bin)"
 47     @$(cc) $^ $(LD_FLAGS) $@
 48 %.o:%.c								#全部展开
 49     @$(Echo) "我要开始编译了...$< -> $@"
 50     @$(cc) $(Flags) $<
 51 
 52 .PHONY:clean
 53 clean:
 54     @$(Rm) $(Obj) $(Bin)
 55     
 56 .PHONY:debug
 57 debug:
 58     @$(Echo) "Bin:$(Bin)"
 59     @$(Echo) "Obj:$(Obj)"
 60     @$(Echo) "Src:$(Src)"

总结

🍓 我是晨非辰Tong!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!

结语:

本篇从核心关系、基础规则,讲到依赖推导、变量优化和多文件工程化,帮你打通make与Makefile的使用逻辑。
吃透语法规范、伪目标、时间戳判断和自动化语法,既能简化编译流程、提升开发效率,也能培养工程化思维,后续结合项目实操即可灵活运用。

在这里插入图片描述

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐