news 2026/4/29 11:18:18

来吧,一篇搞懂嵌入式链接文件!

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
来吧,一篇搞懂嵌入式链接文件!

做嵌入式开发的朋友,大概率都遇到过这样的场景:编译完一个工程,输出目录里一堆文件,.axf.elf.bin.hex.sct.ld…… 后缀五花八门,看着就头大。

很多人只知道 “这个是用来烧录的,那个是用来调试的”,但到底它们是怎么来的?彼此之间有什么关系?为什么 Keil 和 GCC 生成的文件后缀还不一样?

今天我们就从嵌入式软件的完整编译流程出发,把这些文件的来龙去脉一次性讲清楚,看完你再也不会搞混这些后缀了。

先看总览:从代码到运行的完整流程

不管你用的是 Keil MDK,还是 GCC ARM 工具链,整个从写代码到芯片运行的逻辑完全一致,区别只是不同工具链对产物的命名不一样。

接下来我们就顺着这个流程,一步步拆解每个阶段的文件到底是什么。


第一步:编译阶段,生成.o目标文件

一切的起点,是我们自己写的代码:.c源文件和.h头文件,这部分大家都很熟悉,就不多说了。

编译器(比如 Keil 的armcc,或者 GCC 的arm-none-eabi-gcc)会把每个.c文件单独进行编译,把 C 代码转换成处理器能看懂的机器码,最终生成一个对应的.o文件,也就是目标文件(Object File)

这里要注意,这个阶段的编译是 “单文件” 的:每个.c文件只关心自己的代码,不管其他文件的函数、变量在哪里。所以生成的.o文件里,所有的地址都是相对地址,函数调用、变量访问都还没有绑定到最终的绝对内存地址。

简单来说,.o文件就是一个 “半成品”,它只包含了当前这个源文件的机器码,还需要下一步的链接操作,把所有的半成品拼起来,分配最终的地址。


第二步:链接的 “导航图”——.sctvs.ld链接脚本

有了一堆.o半成品,接下来就要进入链接阶段了。链接器要做的事情,就是把所有的.o文件、还有用到的库文件整合到一起,给所有的函数、变量分配最终的绝对地址,把零散的模块拼成一个完整的程序。

但问题来了:链接器怎么知道,哪些代码要放到 Flash 里?哪些数据要放到 RAM 里?Flash 的起始地址是多少?RAM 有多大?有些要搬到 RAM 里运行的代码,要怎么处理?

这就是链接脚本的作用了!它就是给链接器看的 “导航图”,告诉链接器整个芯片的内存布局,以及各个代码段、数据段要放到哪个地址。

而这里,Keil 和 GCC 就出现了第一个命名差异:

  • Keil MDK 用的是.sct文件:全称是 Scatter-Loading Description File,也就是分散加载描述文件

  • GCC ARM 用的是.ld文件:全称是 Linker Script,也就是链接器脚本

它们的核心功能完全一致,都是用来定义内存布局、指导链接器工作的,只是语法和表述方式不一样:

.sct文件用Load Region(加载域,也就是数据烧录到 Flash 的地址)和Execution Region(执行域,也就是程序运行时的地址)的概念,把加载地址和运行地址明确分离开,比如你要把一部分代码从 Flash 加载到 RAM 里运行,在 sct 里可以很清晰地定义出来。

.ld文件的语法更偏向声明式,类似 C 语言的风格,先定义MEMORY区域(比如 Flash 和 RAM 的起始地址和大小),然后在SECTIONS里把各个段(.text代码段、.data数据段等)分配到对应的内存区域里。

举个最简单的例子,STM32F103 的默认链接配置:

在 sct 里,你会看到类似这样的定义:

LR_IROM1 0x08000000 0x00080000 { ; 加载域,Flash的起始地址和大小 ER_IROM1 0x08000000 0x00080000 { ; 执行域,代码运行在Flash *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x20000000 0x00010000 { ; 数据运行在RAM .ANY (+RW +ZI) } }

而在 ld 文件里,对应的定义是这样的:

MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K } SECTIONS { .text : { *(.text) /* 其他代码段 */ } > FLASH .data : { *(.data) /* 其他数据段 */ } > RAM AT > FLASH }

不管语法怎么变,核心都是一件事:告诉链接器,内存怎么分,代码放哪里,数据放哪里。没有这个文件,链接器根本不知道怎么把零散的.o文件拼成一个能在芯片上运行的程序。


第三步:完整版可执行文件 ——.axfvs.elf

有了链接脚本的指导,链接器就可以开始工作了,把所有的.o文件整合起来,分配好地址,最终生成一个完整的可执行文件。

这里,Keil 和 GCC 又出现了第二个命名差异:

  • Keil MDK 生成的是.axf文件:全称是 ARM eXecutable Format,ARM 可执行格式。

  • GCC ARM 生成的是.elf文件:全称是 Executable and Linkable Format,可执行链接格式。

很多人会问,这两个有什么区别?其实答案很简单:.axf本质上就是.elf格式的 ARM 扩展

它们的底层都是标准的 ELF 格式,只是.axf在标准 ELF 的基础上,额外增加了一些 ARM 特有的调试信息、重定位信息,用来适配 Keil 的调试工具链。

这两个文件有个共同的特点:它们非常 “完整”,甚至有点 “臃肿”

它们里面除了真正要运行的机器码(代码段、初始化数据段)之外,还包含了:

  • 完整的符号表:所有函数、变量的名字和地址

  • 源码行号映射:把机器码的地址对应到源代码的行号

  • 调试信息:变量的类型、函数的调用关系等等开发阶段需要的信息

所以这两个文件的体积通常都很大:比如一个小的 STM32 工程,最终的烧录固件可能只有 10KB,但.axf或者.elf文件可能有几百 KB,多出来的大部分都是调试信息。

那它们的用途是什么?它们是给调试用的!

比如你在 Keil 里点击 Debug,下载到芯片里的就是这个.axf文件;你用 J-Link 的 J-Scope 监控变量,也需要加载这个文件。只有有了这些调试信息,你才能在 IDE 里看到源代码、下断点、单步执行、查看变量的值 —— 如果没有这些信息,你只能看到一堆二进制的机器码,根本没法调试。

但是,这些调试信息只有开发阶段才有用,量产烧录的时候,我们根本不需要这些东西,它们只会浪费 Flash 的空间。所以我们还需要下一步,把这些多余的信息去掉,生成精简的烧录固件。


第四步:烧录用的精简固件 ——.binvs.hex

为了得到可以烧录到芯片里的精简固件,我们会用工具(Keil 的fromelf,或者 GCC 的objcopy)把.axf/.elf里的调试信息、符号表这些没用的东西全部去掉,只保留真正要烧录到 Flash 里的机器码和初始化数据。

最终生成的,就是我们最常用的两种烧录文件:.bin.hex

这两个文件的区别,很多人一直搞不清,其实一句话就能说清楚:一个是纯二进制,一个是带地址信息的文本格式

.bin:纯二进制固件

.bin是最纯粹的二进制文件,它把所有要烧录的有效数据,按地址从小到大的顺序,直接排列起来,没有任何额外的信息。

它的优点很明显:体积最小,没有任何多余的开销,10KB 的固件就是 10KB 的文件,一点都不浪费。

但是它有个很大的限制:它默认你的固件的所有地址是连续的

举个例子:如果你的固件所有的代码和数据,都是从0x08000000开始,连续的 10KB 空间,那 bin 文件完全没问题,烧录器把这 10KB 的数据从0x08000000开始写进去就行。

但如果你的固件有非连续的地址呢?比如:

  • 你的 Bootloader 在0x08000000,App 在0x08008000,还有一部分配置数据在0x08010000,中间空了很多区域。

  • 或者你用了分散加载,把一部分数据放到了 Flash 的其他位置。

这时候 bin 文件就处理不了了:因为它是连续的,它会把从最低地址到最高地址之间的所有空间,不管你有没有用到,都打包进去,导致文件变得巨大,而且烧录的时候还会把中间空的区域也擦写,这显然不是我们想要的。

.hex:带地址的通用固件

.hex全称是 Intel HEX 文件,是一种文本格式的固件文件。

它的每一行都是一条记录,里面包含了:这部分数据的起始地址、数据长度、具体的数据,还有校验和。

比如你打开一个 hex 文件,会看到类似这样的内容:

每一行开头的:是标记,然后是长度、地址、类型、数据、校验和。

这种格式的好处是什么?它可以处理非连续的地址!

比如刚才的例子,它可以先写0x08000000开始的 Bootloader,然后跳过中间的空区域,再写0x08008000开始的 App,然后再写0x08010000开始的配置数据。中间的空区域不需要管,也不会占用文件的空间。

而且它自带校验和,烧录器可以自动校验每一行的数据有没有出错,可靠性更高。

当然,它也有缺点:因为是文本格式,每个字节的二进制要转成两个十六进制字符,还要加上地址、校验这些额外的信息,所以它的体积会比 bin 文件大一点,通常会大 30% 左右,但对于现在的存储来说,这点差距完全可以忽略。

所以总结一下:

  • 如果你的固件地址是连续的,用 bin 没问题,体积小一点。

  • 如果你有非连续的地址,或者你不确定,直接用 hex 就对了,兼容性更好,这也是为什么大部分开发工具默认生成 hex 文件的原因。


最后:烧录与运行

拿到了 bin 或者 hex 文件,我们就可以把它烧录到芯片的 Flash 里了。

等芯片上电之后,它会从固定的入口地址(比如 STM32 的0x08000000)开始取指令,我们的程序就正式跑起来了。

到这里,整个从代码到运行的流程就走完了,所有的文件我们也都拆解清楚了。


一张表总结所有文件

最后,我们把所有的文件整理成一

文件后缀

对应工具链

所属阶段

核心作用

.c/.h

通用

源码阶段

开发者编写的源代码

.o

通用

编译阶段

单个源文件编译后的可重定位目标文件

.sct

Keil MDK

链接配置

Keil 的分散加载描述文件,定义内存布局

.ld

GCC ARM

链接配置

GCC 的链接器脚本,定义内存布局

.axf

Keil MDK

链接输出

Keil 的完整可执行文件,含调试信息,用于调试

.elf

GCC ARM

链接输出

GCC 的标准可执行文件,含调试信息,用于调试

.bin

通用

固件输出

纯二进制精简固件,用于连续地址的烧录

.hex

通用

固件输出

Intel HEX 格式固件,支持非连续地址,通用烧录格式


常见疑问解答

1. 为什么调试用 axf/elf,烧录用 bin/hex?

因为调试需要符号表、行号映射这些调试信息,才能让你看到源码、下断点、查看变量,这些信息只有 axf/elf 里有。而烧录只需要真正的机器码,调试信息没用,去掉之后体积更小,烧录更快。

2. 为什么 Keil 和 GCC 的文件后缀不一样?

这是历史原因,Keil 用的是 ARM 的早期工具链,所以用了自己的一套命名,比如 axf、sct;而 GCC 用的是 Unix 体系下的标准命名,比如 elf、ld。但它们的核心逻辑是完全一样的,只是工具不同,所以后缀不同。

3. 能不能把 axf/elf 直接烧录到芯片里?

理论上可以,但是完全没必要,因为里面有大量没用的调试信息,会浪费很多 Flash 空间,而且很多烧录器也不支持直接烧录 elf/axf 格式。


看完这些,是不是突然发现,这些乱七八糟的后缀,其实就是编译流程不同阶段的产物而已?搞懂了整个流程,不管是 Keil 还是 GCC,不管是什么后缀,你都能一眼看明白它是干嘛的,再也不会搞混了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/29 11:16:48

最佳复古游戏体验设置

最佳复古游戏体验设置 【免费下载链接】BetterJoy Allows the Nintendo Switch Pro Controller, Joycons and SNES controller to be used with CEMU, Citra, Dolphin, Yuzu and as generic XInput 项目地址: https://gitcode.com/gh_mirrors/be/BetterJoy 模拟器&#…

作者头像 李华
网站建设 2026/4/29 11:17:15

为什么锡银凸点要加入银?

在半导体封装进入“无铅化”时代后,纯锡虽然成本低,但在实际应用中暴露出很多致命弱点。在纯锡中加入少量银(通常是 1.0% ~ 3.5%),能在微观结构上引发“脱胎换骨”的变化。主要原因可以归结为以下四大核心优势&#xf…

作者头像 李华
网站建设 2026/4/13 14:40:36

Windows驱动管理终极指南:DriverStore Explorer高效清理系统冗余驱动

Windows驱动管理终极指南:DriverStore Explorer高效清理系统冗余驱动 【免费下载链接】DriverStoreExplorer Driver Store Explorer 项目地址: https://gitcode.com/gh_mirrors/dr/DriverStoreExplorer Windows系统在长期使用过程中会累积大量驱动程序文件&a…

作者头像 李华
网站建设 2026/4/14 19:53:20

.NET 诊断技巧 | 日志框架原理、手写日志框架学习蒂

一、 什么是 AI Skills:从工具级到框架级的演化 AI Skills(AI 技能) 的概念最早在 Claude Code 等前沿 Agent 实践中被强化。最初,Skills 被视为“工具级”的增强,如简单的文件读写或终端操作,方便用户快速…

作者头像 李华
网站建设 2026/4/14 23:24:52

AI Agent 跑完任务怎么通知你?我写了个微信推送服务追

1、普通的insert into 如果(主键/唯一建)存在,则会报错 新需求:就算冲突也不报错,用其他处理逻辑 回到顶部 2、基本语法(INSERT INTO ... ON CONFLICT (...) DO (UPDATE SET ...)/(NOTHING)) 语…

作者头像 李华
网站建设 2026/4/15 1:36:37

PyTorch 2.8分布式训练实战:基于RTX 4090D多卡加速大模型预训练

PyTorch 2.8分布式训练实战:基于RTX 4090D多卡加速大模型预训练 1. 多卡训练效果惊艳展示 当面对参数量超过百亿的大模型预训练任务时,单张显卡往往显得力不从心。我们实测在8台配备RTX 4090D的服务器集群上,使用PyTorch 2.8的分布式数据并…

作者头像 李华