本文由 AI 辅助生成并经自动审查;尚未完成真实硬件验证。涉及具体芯片、时序和电气行为时,请以文末第一方资料和实际测试为准。
第 8 课:把一个翻译单元编译成目标文件 #
你已经见过源文件,也知道名字能否跨文件关联。本课只看中间的一步:编译器接收一个翻译单元,成功时生成目标文件,发现问题时给出诊断。目标文件是后续链接步骤的输入,不是可直接运行的程序。
1. 本节目标 #
能在 Windows 主机上对最小 C11 翻译单元执行“只编译、不链接”,依据退出状态、目标文件和诊断判断编译是否成功,并说清 app.c -> 编译器 -> app.o -> 后续链接步骤 的边界。预计用时 30 分钟。
本课只有一个新核心概念:编译阶段的输入、输出与诊断职责。
2. 它解决什么问题 #
直接执行 gcc app.c -o app.exe 会把多个工具阶段藏在一条命令后面。一旦失败,初学者容易把“编译器不接受源代码”和“尚未产生可执行程序”混为一谈。
本课把编译单独停下来:输入是什么,成功输出是什么,失败时看什么。约束是只讨论这条边界,不展开预处理替换、汇编语言、目标文件内部格式、符号表或链接算法。
3. 必要前置知识 #
沿用已学的 main、函数调用、uint32_t、对象、存储期和链接属性。
翻译单元(translation unit)是预处理后交给编译器的一份 C 源代码输入。本课用 app.c 表示它,但不讲预处理器如何展开。链接(link)只作为下一步骤的名字出现:它消费目标文件;内部机制留到后续课程。
4. 核心原理 #
编译器(compiler)把翻译单元转换为目标文件,并报告诊断(diagnostic)。诊断是错误、警告等工具反馈,文字和严重级别可能随编译器及版本变化,不能背某一句输出当作 C11 规则。
只编译(compile only)表示执行编译而不执行最终链接。Arm Compiler for Embedded 6.17 的 -c 说明明确区分 compilation step 与 link step,并说明每个源文件会生成一个 .o 目标文件。[1] GCC 16.2.0 的 -c 同样规定“编译或汇编,但不链接”,最终输出是各源文件对应的目标文件。[2]
目标文件(object file)是供后续链接步骤消费的中间产物。即使 app.o 已存在,本课也不能说程序已经可以运行。
5. 关键术语与直观模型 #
| 术语 | 直观含义 | 本课实例 |
|---|---|---|
| 翻译单元 | 编译器本次读取的一份代码输入 | 由 app.c 形成 |
| 编译器 | 检查输入并尝试产生中间产物的工具 | gcc |
| 目标文件 | 编译成功后的中间产物 | app.o |
| 诊断 | 工具对输入问题的反馈 | PowerShell 中的错误或警告 |
| 只编译 | 停在目标文件,不做最终链接 | GCC 的 -c |
| Host Fake | 主机替身验证 | Windows 上的主机 GCC 实验 |
-c 和 -std=c11 是工具选项,不是 C11 语法。GCC 的 -std=c11 要求按 ISO C11 方言处理输入;不同编译器可能使用不同选项。[3]
6. 从输入到结果的完整流程 #
正常路径:
学习者/构建命令 -- app.c、选项 --> 编译器 -- 成功 --> 文件系统中的 app.o
|
+-- 失败 --> 诊断与非零退出状态
app.o -- 将来由后续步骤读取 --> 链接器(本课到此为止)app.c在实验目录中定义;文件系统持有输入。- 学习者执行的构建命令把输入路径与选项交给编译器。
- 编译器读取输入,并产生
app.o或诊断。 - 文件系统持有产物;后续链接步骤将消费它。
- 本例没有运行时依赖对象,也没有依赖注入者。命令传参只是工具输入,不能称为运行时依赖注入。
判断成功不能只看屏幕是否“安静”:至少同时检查命令退出状态和本次新产生的 app.o。旧文件可能误导判断,因此正常与错误实验要使用不同的新目录或不同输出名。
7. 嵌入式系统中的对应位置 #
主机编译器与 STM32 交叉编译器都需要接收源代码并为后续构建阶段产生输入,这是可迁移的机制。具体 STM32 工程还会选择目标架构、启动文件和其他选项,但本课没有开发板、工具版本或工程证据,不猜测 F4、F7、H5、H7 的命令行差异。
迁移时只问三个通用问题:实际调用了哪个编译器?只编译选项是什么?每个 .c 对应的目标文件和诊断保存在哪里?具体答案必须依据所用工具链官方文档和真实构建日志核对。
8. 主机实验与硬件迁移边界 #
Host Fake 能验证主机工具是否接受这份 C11 输入、是否产生目标文件,以及明显语法错误能否触发诊断。它不能证明代码已为 STM32 构建、已下载或已在硬件上运行,也不能验证引脚、时钟、寄存器或外设。
本课没有真实构建记录和上板证据,以下均为待学习者执行的示例,hardware_verified=false。
9. 最小代码示例 #
创建全新的实验目录,并新建 app.c:
#include <stdint.h>
static uint32_t add_one(uint32_t value)
{
return value + 1U;
}
int main(void)
{
const uint32_t result = add_one(41U);
return (result == 42U) ? 0 : 1;
}代码属于单一应用源文件:add_one 在 app.c 中定义,定义同时给出函数实现,main 在同一文件中调用它。本例没有需要实例化或组装的运行时对象,也没有动态内存;内部辅助函数的参数和返回值使用固定宽度整数。
只编译命令:
gcc --version
gcc -std=c11 -Wall -Wextra -Wpedantic -c .\app.c -o .\app.o
$LASTEXITCODE
Get-Item .\app.o | Select-Object Name, Length, LastWriteTime命令意图是生成 app.o,不是运行程序。课程没有替你执行它。
10. 常见错误 #
- 把
.o当作可执行文件,尝试./app.o。 - 忘记
-c,无意间进入链接步骤。 - 只因没有屏幕输出就判断成功,未检查退出状态和产物。
- 错误实验复用旧的
app.o,把旧产物误认为本次成功输出。 - 把 GCC 的选项、扩展名或诊断原文说成所有 C11 编译器的统一规定。
- 看到错误后立刻猜硬件故障;本实验尚未接触硬件。
11. 调试观察点 #
- 输入:确认命令读取的是当前目录的
app.c。 - 工具:记录
gcc --version,避免比较不同版本的诊断原文。 - 退出状态:PowerShell 中紧接命令读取
$LASTEXITCODE;0通常表示命令成功,非零表示失败。 - 产物:确认
app.o的时间戳属于本次运行,且大小大于零。 - 阶段:本课没有生成
.exe,所以没有运行时输出可观察。
12. 实战实验 #
实验 A:正常只编译 #
执行第 9 节命令,记录编译器版本、完整命令、退出状态以及 app.o 的名称、大小和时间戳。验收条件是退出状态为 0,且本次产生了非空 app.o。这只证明主机编译步骤成功。
实验 B:受控诊断 #
另建空目录,复制代码为 app-bad.c,只删除 return value + 1U; 末尾的分号。先预测结果,再执行:
gcc -std=c11 -Wall -Wextra -Wpedantic -c .\app-bad.c -o .\app-bad.o
$LASTEXITCODE
Test-Path .\app-bad.o预期 GCC 报告语法相关诊断并返回非零状态;新目录中不应出现可用的 app-bad.o。实际措辞以你的版本为准。若结果不同,先确认分号确已删除、路径正确、目录中没有旧产物,再保存完整输出,不要凭印象改结论。
失败排查:
- 找不到
gcc:先确认 GCC 已安装且其目录已加入PATH,重新打开 PowerShell 后执行gcc --version。 - 实验 A 返回非零状态:保存完整诊断,确认当前目录存在未改动的
app.c,并核对命令中的输入与输出路径。 - 实验 A 返回
0但找不到非空app.o:确认读取$LASTEXITCODE前没有执行其他本机程序,再检查-o .\app.o指向的目录和时间戳。 - 实验 B 返回
0或产生了可用的app-bad.o:确认删除的是return value + 1U;末尾分号、使用的是新目录,且命令读取的确为app-bad.c。
13. 自检题 #
- 编译阶段解决什么问题?输入、成功输出和失败反馈分别是什么?
app.c与app.o在哪里,由谁持有?谁把输入和选项交给编译器?- 为什么存在
app.o仍不能直接声称程序可运行? - 为什么判断失败时必须防止旧目标文件干扰?
- 本例是否存在运行时依赖注入?为什么?
14. 面试题 #
问:gcc -c app.c -o app.o 做了什么,没有做什么?
答题要点:它让 GCC 编译或汇编输入但不链接,[2] 成功时产生目标文件供后续链接使用;它没有生成最终可执行程序,也没有证明代码已在目标硬件运行。-c 是 GCC 工具行为,不是 C11 关键字。
15. 延伸思考 #
- 自动构建系统如何避免把旧
app.o当成本次成功产物? - 若一个命令接收两个
.c且使用-c,你预期会有几个目标文件?用官方手册验证,而不是猜测。 - 迁移到交叉编译器后,哪些观察项仍然适用,哪些必须重新查工具文档?
16. 本节总结 #
编译器以翻译单元为输入;成功时产生供后续链接使用的目标文件,失败时通过诊断和退出状态反馈问题。只编译把这一步从完整构建中隔离出来。判断结果要同时看工具版本、退出状态和本次产物,不能把目标文件当作可执行程序。
17. 下一步 #
先完成两个主机实验并回答自检题。课程生成本身不会提高掌握度,也不会更新 completed;下一主题由后续计划决定。
18. 参考资料 #
- Arm, Arm Compiler for Embedded Reference Guide,Document ID
101754_0617_00_en,Version 6.17,Issue 00,2021-10-20;访问日期:2026-09-27。第 2.1 节说明工具输入与产物,第 3.1.3 节说明-c执行编译但不链接。[1] - GNU Project, Using the GNU Compiler Collection (GCC): Overall Options,GCC 16.2.0,第 3.2 节;访问日期:2026-09-27。支撑 GCC
-c的只编译行为。[2] - GNU Project, Using the GNU Compiler Collection (GCC): C Dialect Options,GCC 16.2.0,第 3.4 节;访问日期:2026-09-27。支撑
-std=c11的工具选项语义。[3]