Makefile 完全指南:从入门到工程化,自动化构建不再难


🔥草莓熊Lotso: 个人主页
❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!


🎬 博主简介:


文章目录

  • 前言:
  • [一. Makefile 核心认知:什么是 Makefile?](#一. Makefile 核心认知:什么是 Makefile?)
    • [1.1 背景介绍](#1.1 背景介绍)
    • [1.2 核心作用](#1.2 核心作用)
    • [1.3 核心概念](#1.3 核心概念)
  • [二. 入门案例:最简单的 Makefile](#二. 入门案例:最简单的 Makefile)
    • [2.1 源文件hello.c](#2.1 源文件hello.c)
    • [2.2 基础 Makefile](#2.2 基础 Makefile)
    • [2.3 实际操作演示](#2.3 实际操作演示)
  • [三. Makefile 语法规则:深入理解依赖与命令](#三. Makefile 语法规则:深入理解依赖与命令)
    • [3.1 基本语法格式](#3.1 基本语法格式)
    • [3.2 依赖关系与推导](#3.2 依赖关系与推导)
    • [3.3 伪目标(.PHONY)](#3.3 伪目标(.PHONY))
  • [四. 进阶用法:变量与函数(工程化必备)](#四. 进阶用法:变量与函数(工程化必备))
    • [4.1 变量定义与使用](#4.1 变量定义与使用)
    • [4.2 常用函数(简化文件列表)](#4.2 常用函数(简化文件列表))
    • [4.3 自动变量(简化命令)](#4.3 自动变量(简化命令))
    • [4.4 工程化示例(多源文件适配,简化 Makefile/makefile)](#4.4 工程化示例(多源文件适配,简化 Makefile/makefile))
  • 结尾:

前言:

在 Linux C/C++ 开发中,当项目源文件增多时,手动输入gcc命令编译会变得繁琐且容易出错 ------ 不仅要记住文件依赖关系,还要处理编译顺序和参数。Makefile 的核心价值就是 "自动化构建":一旦写好配置,只需执行make命令,就能自动完成编译、链接、清理等一系列操作,极大提升开发效率。本文从 Makefile 的基本原理入手,逐步拆解语法规则、依赖关系、伪目标、变量与函数,最后通过工程化示例演示实战用法,帮你从 "手动编译" 升级到 "自动化构建",轻松应对中小型项目开发。


一. Makefile 核心认知:什么是 Makefile?

1.1 背景介绍

  • 会不会写makefile,从侧面说明了一个人是否具备完成大型工程的能力
  • 一个工程中的源文件不计其数,其按类型,功能,模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于更复杂的操作
  • makefile 带来的好处就是 -- "自动化编译",一旦写好,只需要一个 make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
  • make 是一个命令工工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi 的 make,Visual C++ 的 namke,Linux下GNU的make。可见,makefile都成为了一种在工厂方面的编译方法。
  • make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。

1.2 核心作用

  • 定义源文件之间的依赖关系(如可执行文件依赖目标文件,目标文件依赖源文件);
  • 指定编译规则(如如何将.c文件编译为.o文件,如何链接为可执行文件);
  • 支持增量编译(只重新编译修改过的文件,减少重复工作);
  • 提供项目清理功能(一键删除编译产物,方便重新构建)。

1.3 核心概念

  • make:解释 Makefile 规则的命令工具,默认查找当前目录下名为Makefile或makefile的文件;
  • 目标(target):要生成的文件或要执行的命令(如可执行文件、清理操作);
  • 依赖(prerequisites):生成目标所需要的文件或条件(如生成myproc依赖myproc.c);
  • 命令(command):生成目标的具体操作(如gcc -o myproc myproc.c),必须以 Tab 键开头。

二. 入门案例:最简单的 Makefile

假设项目只有一个源文件hello.c,要生成可执行文件hello,并支持清理编译产物。

2.1 源文件hello.c

bash 复制代码
#include<stdio.h>
 
int main()
{
	printf("hello Makefile!");
	return 0;
}

2.2 基础 Makefile

bash 复制代码
# 目标:依赖
code: code.c
	# 命令(必须Tab开头):将code.c编译为可执行文件code
	gcc -o code code.c

# 伪目标:清理编译产物
.PHONY: clean
clean:
	# -f表示强制删除,忽略不存在的文件
	rm -f code

2.3 实际操作演示

bash 复制代码
[Lotso@VM-4-4-centos lesson10]$ make
gcc -o hello hello.c
[Lotso@VM-4-4-centos lesson10]$ ll
total 912
-rwxrwxr-x 1 Lotso Lotso   8360 Nov 20 13:45 hello
-rw-rw-r-- 1 Lotso Lotso     81 Nov 20 13:44 hello.c
-rw-rw-r-- 1 Lotso Lotso     76 Nov 20 13:00 hello_copy.c
-rwxrwxr-x 1 Lotso Lotso   8360 Nov 20 09:23 hello_dynamic
-rwxrwxr-x 1 Lotso Lotso 861216 Nov 20 09:22 hello_static
-rw-rw-r-- 1 Lotso Lotso     69 Nov 20 13:44 makefile
-rwxrwxr-x 1 Lotso Lotso   8424 Nov 19 23:39 soft
-rw-rw-r-- 1 Lotso Lotso    250 Nov 19 22:27 soft.c
-rw-rw-r-- 1 Lotso Lotso     90 Nov 19 23:11 soft.cpp
-rwxrwxr-x 1 Lotso Lotso   8968 Nov 19 23:12 softpp
[Lotso@VM-4-4-centos lesson10]$ ./hello
hello Makefile!
[Lotso@VM-4-4-centos lesson10]$ make clean
rm -f hello
[Lotso@VM-4-4-centos lesson10]$ ll
total 900
-rw-rw-r-- 1 Lotso Lotso     81 Nov 20 13:44 hello.c
-rw-rw-r-- 1 Lotso Lotso     76 Nov 20 13:00 hello_copy.c
-rwxrwxr-x 1 Lotso Lotso   8360 Nov 20 09:23 hello_dynamic
-rwxrwxr-x 1 Lotso Lotso 861216 Nov 20 09:22 hello_static
-rw-rw-r-- 1 Lotso Lotso     69 Nov 20 13:44 makefile
-rwxrwxr-x 1 Lotso Lotso   8424 Nov 19 23:39 soft
-rw-rw-r-- 1 Lotso Lotso    250 Nov 19 22:27 soft.c
-rw-rw-r-- 1 Lotso Lotso     90 Nov 19 23:11 soft.cpp
-rwxrwxr-x 1 Lotso Lotso   8968 Nov 19 23:12 softpp


核心用法

  • 编译项目: 在 Makefile 所在目录执行make,自动查找第一个目标(code),检查依赖是否更新,执行编译命令;
  • 清理项目: 执行make clean,执行clean目标下的命令,删除可执行文件;
  • 增量编译: 修改code.c后再次执行 make,只会重新编译修改后的文件,而非全部重编。

三. Makefile 语法规则:深入理解依赖与命令

3.1 基本语法格式

bash 复制代码
目标(target): 依赖(prerequisites)
	命令1
	命令2
	...
  • 目标可以是可执行文件、目标文件(.o)、伪目标(如clean);
  • 依赖可以是源文件、目标文件、其他目标;
  • 命令必须以Tab 键 开头(不能用空格,否则make会报错);
  • 注释以#开头,单行有效。

3.2 依赖关系与推导

参考图示 :(Makefile 会自动推导目标文件的依赖)

但是我们一般是用不到这么多步的,最佳实践如下:

bash 复制代码
# 最终目标:可执行文件code.exe,依赖code.o
code.exe: code.o
	gcc code.o -o code.exe

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

# 伪目标clean
.PHONY: clean
clean:
	rm -f code.exe code.o

3.3 伪目标(.PHONY)

  • 伪目标不是实际文件 ,而是一个 "命令标签"(如clean);
  • 作用 :避免项目中存在与伪目标同名的文件,导致make误判为 "目标已存在,无需执行";
  • 语法/.PHONY: 伪目标名,例如/.PHONY: clean,确保make clean总是执行命令。

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

图示理解 :(什么叫总是被执行?)


四. 进阶用法:变量与函数(工程化必备)

当项目源文件增多时,手动写每个文件的依赖和命令会很繁琐。Makefile 支持变量和函数,可简化配置、提高复用性。

4.1 变量定义与使用

变量用于存储重复出现的内容(如编译器、编译参数、文件列表),语法: 变量名=值 ,使用时 $(变量名)

示例

bash 复制代码
# 定义变量:编译器、可执行文件名、目标文件列表、清理命令
CC = gcc          # 编译器
BIN = myproc      # 可执行文件名
OBJ = myproc.o    # 目标文件列表
RM = rm -f        # 清理命令
CFLAGS = -Wall -g # 编译参数(-Wall显示所有警告,-g生成调试信息)

# 最终目标:依赖OBJ变量
$(BIN): $(OBJ)
	$(CC) $(CFLAGS) -o $(BIN) $(OBJ)

# 目标文件依赖源文件(可省略,make自动推导)
myproc.o: myproc.c

# 清理伪目标
.PHONY: clean
clean:
	$(RM) $(BIN) $(OBJ)

4.2 常用函数(简化文件列表)

Makefile 提供内置函数,可自动获取文件列表,无需手动罗列。
(1)wildcard:获取指定模式的文件

bash 复制代码
# 获取当前目录下所有.c文件,存入SRC变量
SRC = $(wildcard *.c)

(2)替换文件后缀

bash 复制代码
# 将SRC中的.c文件替换为.o文件,存入OBJ变量
Obj=$(Src:.c=.o)

4.3 自动变量(简化命令)

Makefile 提供自动变量,替代命令中重复出现的目标和依赖,简化书写:

自动变量 含义 示例
$@ 当前目标文件名 $(CC) -o $@ $^
$^ 所有不重复的依赖文件 $(CC) -o $@ $^
$< 第一个依赖文件名 $(CC) -c $< -o $@

4.4 工程化示例(多源文件适配,简化 Makefile/makefile)

假设项目有多个源文件,Makefile 可自动适配,简化后的 Makefile 如下所示:

bash 复制代码
Bin=code.exe         # 定义变量     
#Src=$(shell ls *.c) # 做法1 -- 采用shell命令行方式,获取当前所有.c文件名    
Src=$(wildcard *.c)  # 做法2 -- 使用 wildcard 函数,获取当前所有.c文件名    
Obj=$(Src:.c=.o)     # 将Src的所有同名 .c 替换成 .o 形成目标文件列表    
Echo=echo               
cc=gcc     
Rm=rm -f    
Flags=-c -Wall       # 编译选项    
LD_Flags=-o          # 链接选项    
    
$(Bin):$(Obj)    
    @$(Echo) "我要开始链接了...$(Obj) -> $(Bin)"    # $@: 代表目标文件名    $^: 代表依赖文件列表    
    @$(cc) $(LD_Flags) $@ $^                            
%.o:%.c                                             # %.c: 展开当前目录下的所有.c  %.o: 同时展开同名.o    
    @$(Echo) "我要开始编译了...$< -> $@"            # @: 不回显命令    
    @$(cc) $(Flags) $<                              # %<: 对展开的依赖.c文件,一个个的交给gcc    
.PHONY:clean    
clean:    
    $(Rm) $(Obj) $(Bin)                             # $(RM): 替换,用变量内容替换它                                            
                           
.PHONY:debug                                                 
debug:                                                                           
    @$(Echo) "Bin: $(Bin)"             
    @$(Echo) "Obj: $(Obj)"                               
    @$(Echo) "Src: $(Src)"               
~

结尾:

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

结语:Makefile 的核心是 "规则定义" 和 "自动化推导",掌握变量、函数和自动变量后,可轻松适配中小型项目的构建需求。对于大型项目(如 Linux 内核),Makefile 会结合更复杂的语法(如条件判断、循环),但基础逻辑不变。建议在实际项目中多动手编写 Makefile,从简单案例逐步过渡到工程化配置,形成自己的模板。后续可进一步学习cmake(跨平台构建工具),但其核心思想与 Makefile 一致,掌握 Makefile 后可快速上手。

✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど

相关推荐
Victor3562 小时前
Netty(30)Netty的性能指标和监控有哪些工具和技术?
后端
代码游侠2 小时前
学习笔记——网络基础
linux·c语言·网络·笔记·学习·算法
Niuguangshuo2 小时前
生成对抗网络(GAN):从博弈到创造的艺术
人工智能·神经网络·生成对抗网络
小二·2 小时前
AI工程化实战《四》:多模态 RAG 全解——让 AI 看懂 PDF 表格、扫描件与流程图
人工智能·pdf·流程图
热爱生活的五柒2 小时前
深度聚类(Deep Clustering)与度量学习(Metric Learning)的共同点和不同点
人工智能·算法·机器学习
玖日大大2 小时前
Sora 2 全面指南:从基础使用到进阶开发的 AI 视频创作宝典
人工智能
吴佳浩2 小时前
Go 1.25.5 通关讲解
后端·面试·go
Victor3562 小时前
Netty(29)如何实现基于Netty的长连接和推送通知?
后端
神一样的老师2 小时前
混合大语言模型与强化学习用于高能效多星调度:从零开始的性能提升
人工智能·深度学习·语言模型