【0基础嵌入式学习日志】Day04:GCC 编译流程、.o 目标文件与 Makefile 分步编译
2026/8/5 3:26:54 网站建设 项目流程

【0基础嵌入式学习日志】Day04:GCC 编译流程、.o 目标文件与 Makefile 分步编译

一、前言

今天继续进行嵌入式 C 语言基础学习。前几天已经完成了 C 工程目录搭建、结构体、故障码、函数封装、结构体指针、多文件拆分以及 Makefile 多源文件编译。

Day03 中,工程已经从单个main.c拆分成了多个.h.c文件:

system.h / system.c sensor.h / sensor.c fault.h / fault.c main.c

但是 Day03 的 Makefile 仍然是一次性把多个.c文件直接编译成最终可执行程序:

gcc src/main.c src/system.c src/sensor.c src/fault.c-Wall-Wextra-Iinclude-obuild/day03_test

这种写法可以运行,但不够接近真实 C 工程的编译过程。因此,Day04 的重点是学习 GCC 的分步编译流程:

.c 源文件 → .o 目标文件 → 可执行程序

也就是先将每个.c文件分别编译成.o目标文件,然后再把多个.o文件链接成最终程序。


二、Day04 学习目标

本次 Day04 主要学习以下内容:

  1. 理解.c.h.o和可执行文件之间的关系;
  2. 理解 GCC 编译过程中“编译”和“链接”的区别;
  3. 学会使用gcc -c生成.o目标文件;
  4. 学会使用多个.o文件链接生成最终可执行程序;
  5. 学会使用 Makefile 管理分步编译流程;
  6. 理解make cleanmakemake run的作用;
  7. 进一步熟悉嵌入式 C 工程的编译组织方式。

三、Day04 工程结构

Day04 工程是在 Day03 的基础上复制并修改得到的,工程结构如下:

day04 ├── Makefile ├── include │ ├── fault_code.h │ ├── fault.h │ ├── sensor.h │ ├── system.h │ └── system_type.h ├── src │ ├── fault.c │ ├── main.c │ ├── sensor.c │ └── system.c └── build ├── fault.o ├── main.o ├── sensor.o ├── system.o └── day04_test

其中:

  • include:存放头文件;
  • src:存放源文件;
  • build:存放编译生成的中间文件和最终程序;
  • Makefile:管理编译、运行和清理命令。

需要注意的是,build目录下的.o文件和day04_test是本地编译生成的产物,一般不会上传到 GitHub,因为它们可以由源码重新生成。


四、.c、.h、.o 和可执行文件的关系

在 C 工程中,常见文件类型有以下几种。

1..c源文件

.c文件是源代码文件,用来写函数的具体实现。

例如:

src/main.c src/system.c src/sensor.c src/fault.c

这些文件是真正写功能逻辑的地方。


2..h头文件

.h文件是头文件,通常用于放:

宏定义 结构体定义 函数声明

例如:

include/system_type.h include/fault_code.h include/system.h include/sensor.h include/fault.h

头文件的作用是让不同.c文件之间能够共享类型、宏定义和函数声明。


3..o目标文件

.o文件是目标文件,是.c文件经过 GCC 编译后生成的中间结果。

例如:

src/main.c → build/main.o src/system.c → build/system.o src/sensor.c → build/sensor.o src/fault.c → build/fault.o

.o文件不能直接运行,但是它已经是源代码编译后的结果。


4. 可执行文件

多个.o文件最终需要链接成一个可执行程序:

build/main.o build/system.o build/sensor.o build/fault.o ↓ build/day04_test

最终生成的day04_test才可以运行。


五、GCC 编译流程理解

Day04 的核心流程可以概括为:

第一步:每个 .c 文件分别编译成 .o 文件 第二步:多个 .o 文件链接成最终可执行程序

对应关系如下:

src/main.c → build/main.o src/system.c → build/system.o src/sensor.c → build/sensor.o src/fault.c → build/fault.o

然后:

build/main.o + build/system.o + build/sensor.o + build/fault.o → build/day04_test

这个流程比 Day03 更接近真实 C 工程的编译方式。


六、为什么要生成 .o 文件?

在小工程中,可以直接一次性编译多个.c文件:

gcc src/main.c src/system.c src/sensor.c src/fault.c-Wall-Wextra-Iinclude-obuild/day03_test

但是在真实嵌入式工程中,源文件可能很多,例如:

main.c uart.c can.c i2c.c spi.c adc.c motor.c sensor.c fault.c system.c

如果每次修改一个文件都重新编译所有.c文件,效率会比较低。

使用.o文件后,可以将每个.c文件先单独编译成目标文件。这样以后如果只修改了某一个.c文件,理论上只需要重新编译对应的.o文件,再重新链接即可。

这就是大型 C 工程和嵌入式工程常用的编译方式。


七、Day04 Makefile 内容

Day04 的Makefile内容如下:

CC = gcc CFLAGS = -Wall -Wextra -Iinclude TARGET = build/day04_test OBJS = build/main.o build/system.o build/sensor.o build/fault.o all: $(TARGET) $(TARGET): $(OBJS) $(CC) $(OBJS) -o $(TARGET) build/main.o: src/main.c $(CC) $(CFLAGS) -c src/main.c -o build/main.o build/system.o: src/system.c $(CC) $(CFLAGS) -c src/system.c -o build/system.o build/sensor.o: src/sensor.c $(CC) $(CFLAGS) -c src/sensor.c -o build/sensor.o build/fault.o: src/fault.c $(CC) $(CFLAGS) -c src/fault.c -o build/fault.o run: $(TARGET) ./$(TARGET) clean: rm -f build/*.o $(TARGET)

需要注意,Makefile 中命令行前面必须使用Tab 键,不能使用普通空格。例如:

$(CC) $(OBJS) -o $(TARGET)

这一行前面必须是 Tab。


八、Makefile 逐句理解

1. 指定编译器

CC = gcc

CC表示 C Compiler,也就是 C 语言编译器。

后面使用:

$(CC)

就等价于:

gcc

2. 指定编译参数

CFLAGS = -Wall -Wextra -Iinclude

其中:

  • -Wall:开启常见警告;
  • -Wextra:开启更多警告;
  • -Iinclude:告诉 GCC 到include目录下查找头文件。

因为代码中使用了:

#include"system.h"#include"sensor.h"#include"fault.h"

这些头文件都在include目录中,所以需要加上:

-Iinclude

3. 指定最终可执行文件

TARGET = build/day04_test

这表示最终生成的程序是:

build/day04_test

运行时可以使用:

./build/day04_test

4. 指定目标文件列表

OBJS = build/main.o build/system.o build/sensor.o build/fault.o

OBJS表示 object files,也就是目标文件列表。

这里的意思是:

最终程序需要 main.o、system.o、sensor.o 和 fault.o

5. 默认目标 all

all: $(TARGET)

当在终端输入:

make

默认执行第一个目标,也就是all

这句的意思是:

执行 make 时,需要生成 $(TARGET)

$(TARGET)对应:

build/day04_test

6. 生成最终程序

$(TARGET): $(OBJS) $(CC) $(OBJS) -o $(TARGET)

展开后等价于:

build/day04_test: build/main.o build/system.o build/sensor.o build/fault.o gcc build/main.o build/system.o build/sensor.o build/fault.o -o build/day04_test

这表示:

要生成 day04_test,必须先有 main.o、system.o、sensor.o 和 fault.o

然后通过 GCC 把多个.o文件链接成最终可执行程序。


7. 编译 main.c

build/main.o: src/main.c $(CC) $(CFLAGS) -c src/main.c -o build/main.o

这表示:

src/main.c → build/main.o

展开后等价于:

gcc-Wall-Wextra-Iinclude-csrc/main.c-obuild/main.o

其中-c表示:

只编译,不链接

所以这一步只生成main.o,不会生成最终程序。


8. 编译 system.c

build/system.o: src/system.c $(CC) $(CFLAGS) -c src/system.c -o build/system.o

这表示:

src/system.c → build/system.o

9. 编译 sensor.c

build/sensor.o: src/sensor.c $(CC) $(CFLAGS) -c src/sensor.c -o build/sensor.o

这表示:

src/sensor.c → build/sensor.o

10. 编译 fault.c

build/fault.o: src/fault.c $(CC) $(CFLAGS) -c src/fault.c -o build/fault.o

这表示:

src/fault.c → build/fault.o

11. 运行程序

run: $(TARGET) ./$(TARGET)

这表示执行:

makerun

时,会运行:

./build/day04_test

12. 清理编译产物

clean: rm -f build/*.o $(TARGET)

这表示执行:

makeclean

时,会删除:

build/*.o build/day04_test

其中:

  • rm表示删除;
  • -f表示强制删除,不提示;
  • build/*.o表示build目录下所有.o文件。

九、编译与运行过程

进入 Day04 工程目录:

cd/root/Embedded_14Days/day04

清理旧编译产物:

makeclean

执行编译:

make

查看build目录:

lsbuild

可以看到:

day04_test fault.o main.o sensor.o system.o

这说明.o目标文件和最终可执行程序都已经成功生成。

运行程序:

makerun

运行结果如下:

LED state:0Voltage:9.50V Current:2.50A temperature:72.00C Fault code: 0x0007 Fault: Low voltage Fault: Over current Fault: Over temperature

程序成功完成了传感器数据更新、故障检测和系统状态打印。


十、Day03 和 Day04 的区别

Day03 的编译方式是:

多个 .c 文件 → 直接生成可执行程序

对应命令类似:

gcc src/main.c src/system.c src/sensor.c src/fault.c-Wall-Wextra-Iinclude-obuild/day03_test

Day04 的编译方式是:

多个 .c 文件 → 多个 .o 文件 → 可执行程序

也就是:

src/main.c → build/main.o src/system.c → build/system.o src/sensor.c → build/sensor.o src/fault.c → build/fault.o build/main.o + build/system.o + build/sensor.o + build/fault.o → build/day04_test

相比 Day03,Day04 更接近真实工程中的编译方式。


十一、遇到的问题和解决方法

1. Makefile 命令前面必须使用 Tab

Makefile 对格式非常敏感,规则下面的命令行前面必须使用 Tab 键。

例如:

all: $(TARGET) $(TARGET): $(OBJS) $(CC) $(OBJS) -o $(TARGET)

如果使用普通空格,可能会出现:

missing separator

因此,在写 Makefile 时要特别注意缩进格式。


2. build 目录下的文件不上传 GitHub

本地编译后,build目录下会生成:

main.o system.o sensor.o fault.o day04_test

这些文件属于编译产物,不是源码。通常不需要上传到 GitHub。

因为只要源码和 Makefile 存在,就可以重新执行:

make

再次生成这些文件。


3. GitHub push 可能受网络影响

上传 GitHub 时,如果出现类似:

Failed to connect to github.com port443

一般是 WSL 网络连接 GitHub 不稳定,不是代码问题。

只要本地已经成功执行:

gitcommit

说明代码已经保存到本地 Git 版本库。网络恢复后重新执行:

gitpush

即可。


十二、今日总结

通过 Day04 的学习,主要掌握了以下内容:

  1. 理解了.c.h.o和可执行文件之间的关系;
  2. 学会了使用gcc -c.c文件编译成.o目标文件;
  3. 理解了-c表示只编译、不链接;
  4. 理解了多个.o文件需要链接后才能生成最终可执行程序;
  5. 学会了使用 Makefile 管理分步编译流程;
  6. 掌握了make cleanmakemake run的基本作用;
  7. 进一步理解了真实 C 工程中的编译组织方式。

Day04 的核心可以总结为一句话:

.c 源文件先分别编译成 .o 目标文件, 多个 .o 文件再链接成最终可执行程序。

这一步对后续学习嵌入式工程、驱动开发、交叉编译和大型项目构建非常重要。


十三、项目源码

本次 Day04 学习代码已上传至 GitHub:

https://github.com/jdai10590-afk/Embedded-C-Learning-Projects/tree/main/day04

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询