014-C语言的Makefile

plana_lab 发布于 13 天前 78 次阅读



前面我们学习了:

结构体
动态内存
文件操作
预处理与头文件
多文件编程

现在假设项目有:

main.c
student.c
student.h
file.c
file.h

以前我们可能直接:

gcc main.c student.c file.c -o student_manager

文件少的时候还好。

但是如果项目变成:

main.c
student.c
student.h
file.c
file.h
menu.c
menu.h
utils.c
utils.h
config.c
config.h

每次都手动输入:

gcc ...

就会越来越麻烦。

而且修改一个文件之后,通常只需要重新编译相关部分,不需要每次手动判断全部文件。

这就是:

Makefile 的作用。


一、什么是 Makefile

Makefile 是一个用于描述:

如何编译项目

的文件。

我们可以在里面写:

哪些文件需要编译
依赖什么文件
使用什么命令
生成什么结果

然后只需要执行:

make

就可以自动完成编译。


二、最简单的 Makefile

假设有:

main.c

里面:

#include <stdio.h>

int main() {
    printf("Hello World!\n");
    return 0;
}

Makefile:

main: main.c
    gcc main.c -o main

然后:

make

就相当于执行:

gcc main.c -o main

三、Makefile 的基本格式

最核心的格式:

目标: 依赖
    命令

例如:

main: main.c
    gcc main.c -o main

这里:

main
↓
目标

main.c
↓
依赖

gcc main.c -o main
↓
命令

四、为什么命令前面必须是 Tab

例如:

main: main.c
    gcc main.c -o main

gcc 前面必须是:

Tab

不能简单使用空格。

也就是说:

main: main.c
[Tab]gcc main.c -o main

如果把 Tab 换成空格,很多 make 实现会报错。

例如常见:

missing separator

所以学习 Makefile 时要特别注意:

命令行开头使用 Tab。


五、目标和依赖

例如:

main: main.c
    gcc main.c -o main

意思可以理解成:

如果 main.c 有变化
↓
重新执行 gcc
↓
生成 main

如果:

main.c

没有变化,而:

main

已经存在:

make

通常就不会重复执行编译命令。

这就是 Makefile 最重要的功能之一:

根据文件时间自动判断是否需要重新构建。


六、多文件项目

假设:

project/
├─ main.c
├─ student.c
└─ student.h

以前:

gcc main.c student.c -o main

Makefile 可以写:

main: main.c student.c
    gcc main.c student.c -o main

执行:

make

即可。


七、三个文件的项目

再复杂一点:

main.c
student.c
file.c

Makefile:

main: main.c student.c file.c
    gcc main.c student.c file.c -o main

这样:

make

就可以:

main.c
student.c
file.c
↓
gcc
↓
main

八、为什么还要目标文件 .o

实际上 C 项目的编译过程通常是:

main.c
↓
main.o

student.c
↓
student.o

file.c
↓
file.o

main.o
+
student.o
+
file.o
↓
链接
↓
main

所以我们可以让 Makefile 更合理地管理:

.c
↓
.o
↓
可执行文件

九、编译一个 .c 为 .o

GCC:

gcc -c main.c -o main.o

这里:

-c

表示:

只编译,不进行最终链接。

例如:

main.c
↓
main.o

十、链接多个 .o

例如:

gcc main.o student.o file.o -o main

就是:

main.o
student.o
file.o
↓
链接
↓
main

这和上一章学习的:

编译
+
链接

完全对应。


十一、Makefile 管理 .o

可以写:

main: main.o student.o file.o
    gcc main.o student.o file.o -o main

main.o: main.c
    gcc -c main.c -o main.o

student.o: student.c student.h
    gcc -c student.c -o student.o

file.o: file.c file.h student.h
    gcc -c file.c -o file.o

执行:

make

Make 会自动判断:

main.c
↓
main.o

student.c + student.h
↓
student.o

file.c + file.h + student.h
↓
file.o

最后
↓
main

十二、依赖关系的重要性

例如:

student.o: student.c student.h

表示:

student.o
依赖
student.c
student.h

所以只要:

student.c

或者:

student.h

发生修改:

student.o

就需要重新生成。


十三、头文件为什么也要写进依赖

例如:

student.c
student.h

student.c:

#include "student.h"

如果你修改:

student.h

比如把:

typedef struct {
    char name[50];
    int age;
} Student;

改成:

typedef struct {
    char name[100];
    int age;
    double score;
} Student;

那么:

student.c

依赖的头文件已经发生变化。

因此:

student.o

也应该重新编译。

所以写:

student.o: student.c student.h

很重要。


十四、Makefile 的完整基本结构

可以写:

main: main.o student.o file.o
    gcc main.o student.o file.o -o main

main.o: main.c
    gcc -c main.c -o main.o

student.o: student.c student.h
    gcc -c student.c -o student.o

file.o: file.c file.h student.h
    gcc -c file.c -o file.o

这是非常值得手写一遍的基础版本。


十五、变量

Makefile 可以定义变量。

例如:

CC = gcc

之后:

$(CC)

就代表:

gcc

例如:

CC = gcc

main: main.o
    $(CC) main.o -o main

十六、CFLAGS

还可以定义:

CFLAGS = -Wall -Wextra

这样:

$(CC) $(CFLAGS)

就会变成:

gcc -Wall -Wextra

例如:

CC = gcc
CFLAGS = -Wall -Wextra

main: main.o
    $(CC) $(CFLAGS) main.o -o main

十七、LDFLAGS 和 LDLIBS

大型项目中还会看到:

LDFLAGS
LDLIBS

例如:

LDLIBS = -lm

表示链接数学库。

不过当前学习阶段:

CC
CFLAGS

先掌握即可。


十八、自动变量

Makefile 里有几个非常重要的特殊变量:

$@
$<
$^

需要重点掌握。


十九、$@

$@ 表示:

当前目标。

例如:

main.o: main.c
    gcc -c main.c -o $@

这里:

$@

就是:

main.o

相当于:

gcc -c main.c -o main.o

二十、$<

$< 表示:

第一个依赖文件。

例如:

main.o: main.c
    gcc -c $< -o $@

这里:

$<
↓
main.c

$@
↓
main.o

所以:

gcc -c main.c -o main.o

二十一、$^

$^ 表示:

所有依赖文件。

例如:

main: main.o student.o file.o
    gcc $^ -o $@

那么:

$^
↓
main.o student.o file.o

$@
↓
main

相当于:

gcc main.o student.o file.o -o main

二十二、三个自动变量总结

$@
↓
目标

$<
↓
第一个依赖

$^
↓
所有依赖

这个非常重要。


二十三、使用自动变量重写 Makefile

前面的:

main: main.o student.o file.o
    gcc main.o student.o file.o -o main

main.o: main.c
    gcc -c main.c -o main.o

student.o: student.c student.h
    gcc -c student.c -o student.o

file.o: file.c file.h student.h
    gcc -c file.c -o file.o

可以变成:

CC = gcc
CFLAGS = -Wall -Wextra

main: main.o student.o file.o
    $(CC) $(CFLAGS) $^ -o $@

main.o: main.c
    $(CC) $(CFLAGS) -c $< -o $@

student.o: student.c student.h
    $(CC) $(CFLAGS) -c $< -o $@

file.o: file.c file.h student.h
    $(CC) $(CFLAGS) -c $< -o $@

已经更加规范了。


二十四、模式规则

如果每个 .c 文件都写:

main.o: main.c
    gcc -c main.c -o main.o

student.o: student.c
    gcc -c student.c -o student.o

file.o: file.c
    gcc -c file.c -o file.o

其实非常重复。

可以使用:

%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

意思是:

任何 .o
↓
如果存在对应的 .c
↓
按照这个规则编译

例如:

main.o
↓
main.c

student.o
↓
student.c

file.o
↓
file.c

都可以匹配。


二十五、使用模式规则

完整:

CC = gcc
CFLAGS = -Wall -Wextra

main: main.o student.o file.o
    $(CC) $(CFLAGS) $^ -o $@

%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

执行:

make

Make 会自动:

main.c
↓
main.o

student.c
↓
student.o

file.c
↓
file.o

最后链接
↓
main

二十六、clean

编译后可能产生:

main
main.o
student.o
file.o

可以添加:

clean:
    rm -f main main.o student.o file.o

然后:

make clean

就可以清理编译产生的文件。

Windows 环境下,如果使用的是 GNU Make,rm 是否可用取决于你的环境;为了跨环境更稳定,也可以专门写适合当前 Shell 的清理命令。


二十七、.PHONY

clean 实际上不是一个真正的文件。

但是假设目录里面刚好存在一个叫:

clean

Make 可能认为:

clean
↓
已经存在

导致不执行。

因此常见写法:

.PHONY: clean

然后:

clean:
    rm -f main main.o student.o file.o

表示:

clean
不是文件
是一个命令目标

二十八、最终推荐的基础 Makefile

对于当前学习阶段,可以使用:

CC = gcc
CFLAGS = -Wall -Wextra

TARGET = main

OBJS = main.o student.o file.o

$(TARGET): $(OBJS)
    $(CC) $(CFLAGS) $^ -o $@

%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

.PHONY: clean

clean:
    rm -f $(TARGET) $(OBJS)

这里:

CC
↓
编译器

CFLAGS
↓
编译选项

TARGET
↓
最终程序名字

OBJS
↓
目标文件列表

二十九、执行 make

假设:

Makefile
main.c
student.c
student.h
file.c
file.h

执行:

make

可能会得到:

main.o
student.o
file.o
main

流程:

main.c
   ↓
main.o
   │
student.c
   ↓
student.o
   │
file.c
   ↓
file.o
   │
   └──────┐
          ↓
       链接
          ↓
         main

三十、第二次执行 make

假设:

make

第一次已经构建完成。

然后再次:

make

如果源文件都没有变化:

不需要重新编译

因为 Make 会检查文件的修改时间和依赖关系。

这就是 Makefile 比手动:

gcc main.c student.c file.c -o main

方便的地方之一。


三十一、修改一个源文件

假设你修改:

student.c

然后:

make

Make 会发现:

student.c
比 student.o 新

于是:

student.c
↓
重新编译
↓
student.o

然后:

main.o
+
student.o
+
file.o
↓
重新链接
↓
main

而:

main.c
file.c

如果没有变化,就不必重新编译。


三十二、修改头文件

假设修改:

student.h

因为:

student.o: student.c student.h

所以:

student.h
↓
影响 student.o
↓
重新编译 student.c

如果其他文件也依赖:

student.h

它们也应该重新构建。

这就是为什么:

依赖关系必须写正确。


三十三、make 命令

最常见:

make

构建默认目标。

清理:

make clean

也可以指定目标:

make main

三十四、默认目标

例如:

main: main.o student.o file.o
    $(CC) $(CFLAGS) $^ -o $@

clean:
    rm -f main main.o student.o file.o

执行:

make

时,Make 默认会处理:

第一个目标

也就是:

main

所以一般把最终程序的目标写在前面。


三十五、多个目标

例如:

all: main

clean:
    rm -f main *.o

可以:

make all

也可以:

make clean

通常会把:

all
clean

都声明为:

.PHONY: all clean

三十六、通配符

Makefile 可以看到:

*.o

例如:

clean:
    rm -f *.o main

表示删除所有:

.o

文件。


三十七、一个更完整的项目

例如:

student_manager/
│
├─ Makefile
├─ main.c
│
├─ student.c
├─ student.h
│
├─ file.c
└─ file.h

Makefile:

CC = gcc
CFLAGS = -Wall -Wextra

TARGET = student_manager

OBJS = main.o student.o file.o

$(TARGET): $(OBJS)
    $(CC) $(CFLAGS) $^ -o $@

main.o: main.c student.h file.h
    $(CC) $(CFLAGS) -c $< -o $@

student.o: student.c student.h
    $(CC) $(CFLAGS) -c $< -o $@

file.o: file.c file.h student.h
    $(CC) $(CFLAGS) -c $< -o $@

.PHONY: clean

clean:
    rm -f $(TARGET) $(OBJS)

三十八、你现在应该理解的完整流程

把上一章和这一章连接起来:

main.c
student.c
file.c
student.h
file.h
        ↓
预处理
        ↓
编译
        ↓
main.o
student.o
file.o
        ↓
链接
        ↓
student_manager

而 Makefile 做的是:

告诉 make:
↓
这些文件有什么依赖
↓
怎么编译
↓
怎么链接
↓
怎么清理

三十九、Makefile 本身不是 C 代码

例如:

CC = gcc

和:

int a = 10;

完全不是一种语言。

Makefile 是给:

make

这个构建工具使用的。

它主要负责:

编译流程管理

而不是程序本身的功能。


四十、Makefile 与 GCC 的关系

可以理解成:

你
↓
make
↓
Makefile
↓
告诉 make 应该执行什么
↓
gcc
↓
编译 C 程序

所以:

GCC
↓
真正负责 C 的编译和链接

Make
↓
负责自动管理这些编译命令

四十一、本章练习

练习 1

创建:

main.c
Makefile

Makefile 实现:

make

可以编译:

main.c

练习 2

创建:

main.c
hello.c
hello.h
Makefile

要求:

make

自动完成:

main.o
hello.o
hello

练习 3

给项目添加:

student.c
student.h

让 Makefile 自动处理:

main.o
student.o
hello.o

最后生成:

student_manager

练习 4

加入:

.PHONY: clean

clean:
    ...

实现:

make clean

删除:

.o
可执行文件

练习 5

自己使用:

$@
$<
$^

重写编译规则。

例如:

main: main.o student.o
    $(CC) $^ -o $@

练习 6

加入:

CFLAGS = -Wall -Wextra

然后:

$(CC) $(CFLAGS)

编译。

观察 GCC 出现警告时的效果。


练习 7

完成你自己的项目:

course_manager/
│
├─ Makefile
├─ main.c
├─ course.c
├─ course.h
├─ file.c
├─ file.h
└─ config.h

要求:

make

完成整个项目的编译。

然后:

make clean

清理构建文件。

此作者没有提供个人介绍。
最后更新于 2026-09-18