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

发布时间:2026/6/28 9:30:05
【0基础嵌入式学习日志】Day04:GCC 编译流程、.o 目标文件与 Makefile 分步编译 【0基础嵌入式学习日志】Day04GCC 编译流程、.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 主要学习以下内容理解.c、.h、.o和可执行文件之间的关系理解 GCC 编译过程中“编译”和“链接”的区别学会使用gcc -c生成.o目标文件学会使用多个.o文件链接生成最终可执行程序学会使用 Makefile 管理分步编译流程理解make clean、make、make run的作用进一步熟悉嵌入式 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 gccCC表示 C Compiler也就是 C 语言编译器。后面使用$(CC)就等价于gcc2. 指定编译参数CFLAGS -Wall -Wextra -Iinclude其中-Wall开启常见警告-Wextra开启更多警告-Iinclude告诉 GCC 到include目录下查找头文件。因为代码中使用了#includesystem.h#includesensor.h#includefault.h这些头文件都在include目录中所以需要加上-Iinclude3. 指定最终可执行文件TARGET build/day04_test这表示最终生成的程序是build/day04_test运行时可以使用./build/day04_test4. 指定目标文件列表OBJS build/main.o build/system.o build/sensor.o build/fault.oOBJS表示 object files也就是目标文件列表。这里的意思是最终程序需要 main.o、system.o、sensor.o 和 fault.o5. 默认目标 allall: $(TARGET)当在终端输入make默认执行第一个目标也就是all。这句的意思是执行 make 时需要生成 $(TARGET)而$(TARGET)对应build/day04_test6. 生成最终程序$(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.cbuild/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.cbuild/system.o: src/system.c $(CC) $(CFLAGS) -c src/system.c -o build/system.o这表示src/system.c → build/system.o9. 编译 sensor.cbuild/sensor.o: src/sensor.c $(CC) $(CFLAGS) -c src/sensor.c -o build/sensor.o这表示src/sensor.c → build/sensor.o10. 编译 fault.cbuild/fault.o: src/fault.c $(CC) $(CFLAGS) -c src/fault.c -o build/fault.o这表示src/fault.c → build/fault.o11. 运行程序run: $(TARGET) ./$(TARGET)这表示执行makerun时会运行./build/day04_test12. 清理编译产物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_testDay04 的编译方式是多个 .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相比 Day03Day04 更接近真实工程中的编译方式。十一、遇到的问题和解决方法1. Makefile 命令前面必须使用 TabMakefile 对格式非常敏感规则下面的命令行前面必须使用 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 的学习主要掌握了以下内容理解了.c、.h、.o和可执行文件之间的关系学会了使用gcc -c将.c文件编译成.o目标文件理解了-c表示只编译、不链接理解了多个.o文件需要链接后才能生成最终可执行程序学会了使用 Makefile 管理分步编译流程掌握了make clean、make、make run的基本作用进一步理解了真实 C 工程中的编译组织方式。Day04 的核心可以总结为一句话.c 源文件先分别编译成 .o 目标文件 多个 .o 文件再链接成最终可执行程序。这一步对后续学习嵌入式工程、驱动开发、交叉编译和大型项目构建非常重要。十三、项目源码本次 Day04 学习代码已上传至 GitHubhttps://github.com/jdai10590-afk/Embedded-C-Learning-Projects/tree/main/day04