↓ 跳过正文
  1. 嵌入式学习笔记/
  2. EmbeddedStudy/

把一个翻译单元编译成目标文件

目录
EmbeddedStudy - 这篇文章属于一个选集。
§ 8: 本文

本文由 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. 调试观察点
#

  1. 输入:确认命令读取的是当前目录的 app.c。
  2. 工具:记录 gcc --version,避免比较不同版本的诊断原文。
  3. 退出状态:PowerShell 中紧接命令读取 $LASTEXITCODE;0 通常表示命令成功,非零表示失败。
  4. 产物:确认 app.o 的时间戳属于本次运行,且大小大于零。
  5. 阶段:本课没有生成 .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. 自检题
#

  1. 编译阶段解决什么问题?输入、成功输出和失败反馈分别是什么?
  2. app.c 与 app.o 在哪里,由谁持有?谁把输入和选项交给编译器?
  3. 为什么存在 app.o 仍不能直接声称程序可运行?
  4. 为什么判断失败时必须防止旧目标文件干扰?
  5. 本例是否存在运行时依赖注入?为什么?

14. 面试题
#

问:gcc -c app.c -o app.o 做了什么,没有做什么?

答题要点:它让 GCC 编译或汇编输入但不链接,[2] 成功时产生目标文件供后续链接使用;它没有生成最终可执行程序,也没有证明代码已在目标硬件运行。-c 是 GCC 工具行为,不是 C11 关键字。

15. 延伸思考
#

  • 自动构建系统如何避免把旧 app.o 当成本次成功产物?
  • 若一个命令接收两个 .c 且使用 -c,你预期会有几个目标文件?用官方手册验证,而不是猜测。
  • 迁移到交叉编译器后,哪些观察项仍然适用,哪些必须重新查工具文档?

16. 本节总结
#

编译器以翻译单元为输入;成功时产生供后续链接使用的目标文件,失败时通过诊断和退出状态反馈问题。只编译把这一步从完整构建中隔离出来。判断结果要同时看工具版本、退出状态和本次产物,不能把目标文件当作可执行程序。

17. 下一步
#

先完成两个主机实验并回答自检题。课程生成本身不会提高掌握度,也不会更新 completed;下一主题由后续计划决定。

18. 参考资料
#

  1. 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]
  2. GNU Project, Using the GNU Compiler Collection (GCC): Overall Options,GCC 16.2.0,第 3.2 节;访问日期:2026-09-27。支撑 GCC -c 的只编译行为。[2]
  3. GNU Project, Using the GNU Compiler Collection (GCC): C Dialect Options,GCC 16.2.0,第 3.4 节;访问日期:2026-09-27。支撑 -std=c11 的工具选项语义。[3]
RuChen
作者
RuChen
EmbeddedStudy - 这篇文章属于一个选集。
§ 8: 本文