从编译到内存全揭秘!STM32工程师必看:点击Build后,代码到底经历了什么?
序言:你是否也被这些问题卡过壳?
点击STM32CubeIDE的“编译”按钮,看着进度条滚动时,你有没有好奇过:一行行C代码是如何变成单片机能执行的二进制文件的?为什么明明定义了变量到CCMRAM,map文件里却跑到了RAM?编译后的数据段、代码段到底藏在Flash还是RAM里?为什么有时候函数必须放到RAM里才能跑?
这些看似“底层”的问题,恰恰是嵌入式开发的“隐形门槛”——搞懂它们,你能轻松解决90%的编译报错、内存溢出、变量异常问题;不懂它们,调试时可能对着乱码的变量一脸懵,甚至在项目上线后踩坑。
这篇文章就带你扒开STM32编译的“黑箱”:从预编译→编译→汇编→链接的完整流程图解,到链接脚本里FLASH.ld和RAM.ld的核心区别;从text/data/bss段的内存分布,到手把手教你把变量“钉死”在CCMRAM的指定地址,再到如何让高频中断函数在RAM里飞跑……
全程干货无废话,既有原理拆解,更有实战案例(附map文件验证截图+避坑指南)。无论你是刚入门的新手,还是想进阶的工程师,读完这篇都能打通“代码→内存”的任督二脉。建议点赞+收藏,下次遇到编译内存问题,直接翻出来对着调!
一、点击工具栏的 🔨 Build 按钮的时候发生了什么
在STM32CubeIDE中, 我们编写完代码,点击“编译”按钮的时候,从C语言源代码到可执行文件经过哪几个步骤?
从源代码到可执行文件(.hex 或 .bin)的生成会经历多个关键步骤,包括 预编译、编译、汇编、链接。以下是详细的流程解析:
1.1 整体编译流程
1.2 各步骤详解
1.2.1 预编译(Preprocessing)
- 作用:处理源代码中的预处理指令,生成“纯净”的代码供编译器使用。
- 处理内容:
#include:展开头文件。#define:宏替换。#ifdef/#endif:条件编译。- 删除注释。
- 工具:预处理器(如
gcc -E)。 - 输出:
.i文件(C 语言)或.ii文件(C++)。
示例:
// 原始代码
#define PI 3.14
float area = PI * r * r;
预编译后:
float area = 3.14 * r * r; // 宏已展开
1.2.2 编译(Compilation)
- 作用:将预编译后的代码(高级语言)转换为汇编代码(低级语言)。
- 关键操作:
- 语法分析、语义分析。
- 生成中间代码。
- 优化(如
-O1,-Os)。
- 工具:编译器(如
arm-none-eabi-gcc)。 - 输出:
.s文件(汇编代码)。
示例:
C 代码:
int add(int a, int b)
{
return a + b;
}
编译为汇编(ARM):
add:
add r0, r0, r1
bx lr
1.2.3 汇编(Assembly)
- 作用:将汇编代码转换为机器码(二进制目标文件)。
- 关键操作:
- 解析汇编指令(如
MOV,ADD)。 - 生成可重定位的目标文件(
.o或.obj)。
- 解析汇编指令(如
- 工具:汇编器(如
arm-none-eabi-as)。 - 输出:
.o文件(目标文件)。
示例:
汇编代码:
add:
add r0, r0, r1
bx lr
汇编后生成二进制机器码(如 0xE0800001)。
1.2.4 链接(Linking)
- 作用:合并多个目标文件(
.o)和库文件(.a/.lib),生成最终的可执行文件。 - 关键操作:
- 符号解析:解决函数/变量的引用(如
printf来自库)。 - 地址分配:为代码和数据分配具体的 Flash/RAM 地址(依赖链接脚本
.ld)。 - 重定位:调整代码中的绝对地址。
- 符号解析:解决函数/变量的引用(如
- 工具:链接器(如
arm-none-eabi-ld)。 - 输出:
.elf(含调试信息)或.hex/.bin(纯二进制)。
链接脚本示例(STM32F4xx_FLASH.ld):
/* Memories definition */
MEMORY
{
CCMRAM (xr) : ORIGIN = 0x10000000, LENGTH = 64K
RAM (xr) : ORIGIN = 0x20000000, LENGTH = 128K
FLASH (xr) : ORIGIN = 0x8000000, LENGTH = 1024K
}
/* Sections */
SECTIONS
{
/* The program code and other data into "FLASH" Rom type memory */
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .; /* define a global symbols at end of code */
} >FLASH
1.2.5 常见问题
Q1:为什么需要链接脚本?
- 单片机没有操作系统,需手动指定代码和数据在 Flash/RAM 中的存放位置。
Q2:.hex 和 .bin 的区别?
.hex 包含地址信息,适合调试;.bin 是纯二进制,适合量产烧录。
- HEX 文件示例
:表示一行记录的开始。10表示数据长度(16 字节)。0000是地址偏移。FF是校验和。
:10000000FFEE00112233445566778899AABBCCDDEE55
:00000001FF
-
BIN 文件示例
- 直接是 MCU Flash 中的二进制数据,无额外信息。
FF EE 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE
以下是具体区别:
- 文件结构与编码方式
| 特性 | HEX 文件 | BIN 文件 |
|---|---|---|
| 格式 | 文本格式(ASCII 编码) | 纯二进制格式(原始数据) |
| 内容 | 包含地址、数据、校验和等元信息 | 仅包含原始二进制数据 |
| 可读性 | 人类可读(可用文本编辑器查看) | 不可读(需专用工具解析) |
| 文件大小 | 较大(因包含额外信息) | 较小(仅存储有效数据) |
- 存储内容对比
| 功能 | HEX 文件 | BIN 文件 |
|---|---|---|
| 地址信息 | 包含(支持分段地址扩展) | 不包含(需指定烧录地址) |
| 数据填充 | 可跳过未使用的地址区域 | 必须连续填充(全地址空间) |
| 校验和 | 每行包含校验和(防止传输错误) | 无校验和 |
| 扩展功能 | 支持启动地址、结束标志等 | 仅存储原始数据 |
- 典型应用场景
| 场景 | HEX 文件 | BIN 文件 |
|---|---|---|
| 调试/开发 | 常用(IDE 默认生成,如 Keil、IAR) | 较少直接使用 |
| 生产烧录 | 需烧录工具解析(如 J-Flash) | 直接烧录(地址需手动指定) |
| OTA 升级 | 较少(因体积大) | 常用(节省带宽) |
| Bootloader | 需解析地址 | 直接拷贝到指定地址 |
Q3:如何减少编译后的代码大小?
- 编译优化(
-Os)。

- 移除未使用的函数(
-ffunction-sections+-Wl,--gc-sections)。


1.2.6 总结
| 步骤 | 输入 | 输出 | 作用 |
|---|---|---|---|
| 预编译 | .c/.h | .i | 展开宏、头文件 |
| 编译 | .i | .s | 高级语言 → 汇编 |
| 汇编 | .s | .o | 汇编 → 机器码(目标文件) |
| 链接 | .o + .ld | .elf/.bin | 合并目标文件,分配地址 |
理解这些步骤有助于调试编译错误、优化代码大小,并解决内存分配问题(如 Bootloader + APP 的分区)。
注:关于Bootloader和APP,我会在将来专门出一篇文章讲解。
二、链接脚本和编译后的信息解读
2.1 链接文件
在STM32CubeIDE的工程中,通常有两个ld文件,即链接文件,分别是:STM32F407ZGTX_RAM.ld和STM32F407ZGTX_FLASH.ld。它们是两个不同的 链接脚本(Linker Script),它们的核心区别在于 程序存储和运行的位置。以下是详细对比和原因分析:
- 核心区别
| 特性 | FLASH.ld (默认) | RAM.ld |
|---|---|---|
| 程序存储位置 | Flash (0x08000000) | RAM (0x20000000) |
| 程序执行位置 | 直接从Flash运行(XIP, Execute-In-Place) | 需将代码从Flash拷贝到RAM后执行 |
| 启动速度 | 较慢(Flash访问延迟) | 较快(RAM零等待周期) |
| 适用场景 | 常规应用(99%情况) | 特殊调试、Flash操作(如Bootloader开发) |
| Flash占用 | 全部代码存储在Flash | 仅初始化数据和中断向量表在Flash |
| RAM占用 | 仅数据段(.data/.bss)和堆栈 | 全部代码+数据需放入RAM |
| 调试便利性 | 支持常规调试 | 可动态修改代码(无需擦写Flash) |
- 为什么STM32CubeIDE默认关联
FLASH.ld?-
常规应用的合理性
- 非易失性存储:Flash断电后代码不丢失,适合量产固件。
- 资源优化:STM32的Flash(1024KB)通常比RAM(128KB)大得多,代码存Flash可节省RAM。
- 执行效率:虽然Flash比RAM慢,但Cortex-M4的预取缓冲(Prefetch)和ART加速器(F4系列)能显著减少性能损失。
-
RAM模式的局限性
- 容量限制:STM32F407的RAM仅128KB(+64KB CCMRAM),无法容纳大型程序。
- 易失性:掉电后代码丢失,每次上电需重新加载(通过调试器或Bootloader)。
- 使用复杂度:需手动管理代码拷贝,增加开发负担。
-
2.3 编译后的信息
STM32CubeIDE编译之后,生成如下信息:

但是在KEIL中编译,会生成如下信息:

下面分别进行解释:
对于STM32CubeIDE,编译后生成的text、data、bss,或者,对于如 Keil、IAR 或 GCC,编译后生成的 Code、RO、RW、ZI ,都是描述程序内存占用的关键指标。它们分别代表不同的内存段,用于分析 Flash 和 RAM 的使用情况。
以下是详细解释:
2.3.1 KEIL编译后的信息详解
2.3.1.1 内存段分类
| 段名 | 全称 | 存储内容 | 存放位置 |
|---|---|---|---|
| Code | Code(代码段) | 程序代码(机器指令)、常量字符串、只读数据(如 const 变量) | Flash |
| RO | Read-Only(只读数据) | 只读数据(如常量数组、const 全局变量)、部分编译器会将 Code 合并到 RO | Flash |
| RW | Read-Write(可读写数据) | 已初始化的全局变量、静态变量(如 int a = 10;) | Flash + RAM |
| ZI | Zero-Init(零初始化段) | 未初始化或显式初始化为 0 的全局变量/静态变量(如 int b; 或 int c = 0;) | RAM |
2.3.1.2 内存分布详解
-
Flash(ROM)存储内容
-
Code + RO:
- 程序代码(函数、指令)。
- 只读数据(如
const uint8_t table[] = {1, 2, 3};)。
-
RW 的初始值:
- 已初始化的全局变量(如
int flag = 1;)的初始值会存储在 Flash 中,程序启动时由启动代码(如startup.s)拷贝到 RAM。
- 已初始化的全局变量(如
-
-
RAM 运行时占用
- RW(运行时):
- 从 Flash 加载初始值后的全局变量(占用 RAM)。
- ZI:
- 未初始化的变量(默认填 0,由启动代码初始化)。
- RW(运行时):
-
示例分析
假设编译后输出如下信息(来自 Keil 或 GCC 的map文件):Program Size: Code = 10240 bytes RO = 2048 bytes RW = 512 bytes ZI = 1024 bytes -
计算总占用
- Flash 总占用 =
Code + RO + RW(初始值)= 10240 + 2048 + 512 = 12800 字节
(RW 的初始值需存储在 Flash 中,运行时拷贝到 RAM)。 - RAM 总占用 =
RW(运行时) + ZI= 512 + 1024 = 1536 字节
- Flash 总占用 =
2.3.2 STM32CubeIDE编译后的信息详解
输出示例:
text data bss dec hex filename
10240 512 1024 11776 2e00 firmware.elf
注意:以上数据全部是十进制!
text= Code + ROdata= RW(初始值在 Flash,运行时在 RAM)bss= ZIdec=text+data+bss,得出十进制的总量,单位字节。hex=dec,只是十六进制的写法。
2.3.3 常见问题
Q1:为什么 RW 既占 Flash 又占 RAM?
- Flash 存储初始值,RAM 存储运行时值。启动时需从 Flash 拷贝初始值到 RAM。
Q2:ZI 段为什么不占 Flash?
- ZI 段变量默认值为 0,无需存储初始值,启动代码直接清零 RAM 即可。
Q3:如何查看具体变量在哪个段?
- 分析
map文件(Keil/IAR/GCC 均会生成),搜索变量名查看所属段。
2.3.4 总结
| 段名 | 存储内容 | 占用位置 | 优化方向 |
|---|---|---|---|
| Code | 程序指令 | Flash | 代码优化、移除未使用函数 |
| RO | 只读数据 | Flash | 压缩常量数据 |
| RW | 已初始化全局变量 | Flash + RAM | 减少全局变量 |
| ZI | 零初始化变量 | RAM | 显式初始化变量,减少全局使用 |
理解这些概念有助于优化嵌入式系统的 Flash 和 RAM 使用,避免资源不足的问题。
三、STM32F4的Flash和RAM
3.1 RAM
我查看芯片手册,对于RAM空间和对应的地址范围,得到下面的结果:

查看参考手册,也得到类似的结论:

那么对于CCM,我有了更多的好奇,因为只能CPU访问,DMA无法访问到。于是附上框图:

如上图,CCM确实只有CPU可访问,而且访问速度会更快。
3.2 FLASH
在STM32F407ZGT6中,有1MB的FLASH空间,可谓是很富有啊!继续查考参考手册,得到下图:


我们简单计算一下,128KB*(11-5+1) + 64KB + 16KB * 4 = 1024KB,也就是1MB。对于下面的系统存储器,OTP区域和选项字节,都不在这1MB的FLASH空间内。而我们常用来存放程序的地址就是主存储器FLASH的扇区0,地址是0x8000 0000。
3.3 Flash 和 RAM 的分工
-
Flash(非易失性):
- 存储程序的 代码(
.text段)、只读数据(.rodata段) 和 初始化数据的初始值(.data段的初始值)。 - 断电后数据不丢失,但访问速度较慢(STM32F4的Flash等待周期约为几十纳秒)。
- 存储程序的 代码(
-
RAM(易失性):
- 存储 运行时变量(全局/静态变量)、堆栈(Stack/Heap) 和 需要频繁修改的数据。
- 访问速度快(STM32F4的RAM通常零等待周期),但断电后数据丢失。
3.4 数据段的处理和启动流程
3.4.1 数据段的处理
-
已初始化全局变量(
.data段)-
存储:初始值保存在Flash中(编译时确定)。
-
运行时:启动时由
startup_*.s中的初始化代码将初始值从Flash拷贝到RAM。示例:
- 定义全局变量 int a = 10;
- 初始值 “10” 存储在Flash中。
- 上电后,启动代码将 “10” 拷贝到RAM的变量地址。
-
-
未初始化全局变量(
.bss段)- 存储:无初始值(或默认为0)。
- 运行时:启动代码将
.bss段对应的RAM区域清零。
-
只读数据(
.rodata段)- 存储:始终留在Flash中,无需拷贝到RAM。
3.4.2 STM32启动流程
STM32上电后,执行顺序如下:
- 硬件复位:PC指针跳转到Flash起始地址(
0x08000000),执行Reset_Handler。 - 初始化.data段:将Flash中的初始值拷贝到RAM的
.data段。 - 清零.bss段:将RAM中的
.bss段全部置零。 - 设置堆栈指针:将
_estack(栈顶地址)赋值给SP寄存器。 - 跳转到main():开始执行用户代码。
四、小试牛刀:定义变量在CCMRAM段
我们已经知道,MCU最快访问的RAM区域是CCMRAM,大小是64KB,起始地址是0x1000 0000。那么如果我们想要使用这部分的内存,该如何实现?
4.1 将变量存放在CCMRAM段
4.1.1 修改ld文件,增加.ccmram段
在STM32F407ZGTX_FLASH.ld文件中,我们看到有如下的定义:
MEMORY
{
CCMRAM (xr) : ORIGIN = 0x10000000, LENGTH = 64K
RAM (xr) : ORIGIN = 0x20000000, LENGTH = 128K
FLASH (xr) : ORIGIN = 0x8000000, LENGTH = 1024K
}
可知,程序有明确划分CCMRAM的存储开始地址,长度信息,都与数据手册里的完全一致。但它后面没有定义针对这个区域的段。所以我们需要加上如下代码:
/* Sections */
SECTIONS
{
//其他的段定义...
/* 定义.ccmram段 */
.ccmram :ALIGN(4)
{
__ccmram_start = .;
*(.ccmram)
__ccmram_end = .;
} >CCMRAM AT>FLASH
}
这就告诉链接器,在最后链接的时候,将存放在.ccmram 段的变量,先存放在FLASH中,并且运行的时候,从FLASH加载到CCMRAM中。
4.1.2 定义变量
大家可以参照我下面的代码:
/*存放在CCRAM中的快速访问变量*/
__attribute__((section(".ccmram"))) char printf_string_g[80];
这样,我们就定义了一个长度是80字节的字符数组。
4.1.3 最终的效果
编译完成后,查看.map文件,通常位于:Debug/ 或 Release/目录下,如 projectname.map。查找我们定义的变量,即可得到如下结果:
.ccmram 0x10000000 0x50 load address 0x08016b94
*(.ccmram)
.ccmram 0x10000000 0x50 ./Core/Src/main.o
0x10000000 printf_string_g
至此,大家应该明白了,.map 文件中.ccmram段的加载地址是0x08016b94(Flash 区域),字符数组变量实际位于CCMRAM中,地址是0x10000000,大小是0x50,也即是80字节。
在调试中,我们进一步验证(这里我又将字符数组的大小改成了200字节,看到差异的小伙伴不要疑惑哦):

4.2 将变量定义于特定的地址
在上一个实例的基础上,我们上升了难度,不但要将变量存放在CCMRAM内存空间,而且要特别指定它的地址,比如是:0x10000200。我尝试过使用:uint16_t my_global_var_uint16_fix __attribute__((section(".ccmram"), at(0x10000200)));但编译器忽略了at导致查看map文件的时候,发现并没有按照我们预期的存放变量。所以不得不花时间探索这个难题,因为在项目中是实在常遇到这样的场景。
4.2.1 修改ld文件
在STM32F407ZGTX_FLASH.ld文件中,定义下面的段:
SECTIONS
{
//其他的段定义...
/*定义CCM区域的固定地址变量*/
.ccmram_fixed 0x10000200: ALIGN(4)
{
__ccmram_fix__start = .;
*(.ccmram_fixed)
__ccmram_fix_end = .;
} >CCMRAM AT>FLASH
}
为了这个地址,特意定义了一个段,加载地址在FLASH,但实际上的RAM地址在0x10000200。
4.2.2 定义变量
这里我特意定义了两个变量,有一个故意初始化一下,但初始化是否管用呢?我们拭目以待!
uint16_t my_global_var_uint16_fix __attribute__((section(".ccmram_fixed")));
__attribute__((section(".ccmram_fixed"))) uint32_t my_global_var_uint32_fix = 0;
4.2.3 最终结果
查看map文件,得到下面的结果:
.ccmram_fixed 0x10000200 0x6 load address 0x08016cb8
0x10000200 __ccmram_fix__start = .
*(.ccmram_fixed)
.ccmram_fixed 0x10000200 0x6 ./Core/Src/main.o
0x10000200 my_global_var_uint32_fix
0x10000204 my_global_var_uint16_fix
0x10000206 __ccmram_fix_end = .
功夫不负有心人,我们成功了!第一个变量确实存放在了0x10000200这个地址,之后第二个存放的地址在后面。不过编译器并没有按照我们定义的顺序存放变量,有点随心所欲了……
4.2.4 初始化管用吗
我在死循环里打印这两个变量,另外我定义了另一个变量:uint8_t my_global_var_uint8 __attribute__((section(".ccmram")));。这是一个存放在CCMRAM段的普通的变量。我们先看下map文件:
.ccmram 0x10000000 0x51 load address 0x08016c64
0x10000000 __ccmram_start = .
*(.ccmram)
.ccmram 0x10000000 0x51 ./Core/Src/main.o
0x10000000 my_global_var_uint8
0x10000001 printf_string_g
0x10000051 __ccmram_end = .
.ccmram_fixed 0x10000200 0x6 load address 0x08016cb8
0x10000200 __ccmram_fix__start = .
*(.ccmram_fixed)
.ccmram_fixed 0x10000200 0x6 ./Core/Src/main.o
0x10000200 my_global_var_uint32_fix
0x10000204 my_global_var_uint16_fix
0x10000206 __ccmram_fix_end = .
下面我特意打印这几个变量的值:
sprintf(printf_string_g, "my_global_var_uint32_fix = %lu\r\nmy_global_var_uint16_fix = %u, my_global_var_uint8 = %u\r\n",
my_global_var_uint32_fix, my_global_var_uint16_fix, my_global_var_uint8);
queue_printf(printf_string_g);
至于函数queue_printf()大家不用疑惑,这是我写的一个串口队列发送函数。下面看下结果:

看的出来,我在上面定义变量的时候,初始化是没有效果的,里面的值是随机值。所以我需要在主函数里,特意将其初始化为0,之后再使用。
/*存放在CCMRAM中的全局变量使用测试*/
my_global_var_uint8 ++;
my_global_var_uint16_fix ++;
my_global_var_uint32_fix ++;
if(my_global_var_uint32_fix % 1000 == 520)
{
sprintf(printf_string_g, "my_global_var_uint32_fix = %lu\r\nmy_global_var_uint16_fix = %u, my_global_var_uint8 = %u\r\n",
my_global_var_uint32_fix, my_global_var_uint16_fix, my_global_var_uint8);
queue_printf(printf_string_g);
}
于是得到下面的结果:

五、STM32一定要将存放在FLASH中的程序拷贝到RAM中再执行吗
不是的!MCU可以直接在FLASH中执行程序。但如果在RAM中运行的话,速度会更快!
5.1 MCU在FLASH或RAM中运行代码的场景
-
代码(函数)直接在Flash中执行:
STM32的Cortex-M内核通过 ICode总线 直接从Flash读取指令并执行,无需将代码拷贝到RAM。- 优点:节省RAM空间(RAM通常比Flash小得多)。
- 缺点:Flash的读取速度比RAM慢(但对大多数应用影响不大)。
-
例外情况(需拷贝到RAM运行的代码):
某些特殊场景(如Flash擦写操作、高频中断服务函数)需要将代码放到RAM中运行,此时需使用__attribute__((section(".RamFunc")))修饰函数,并在链接脚本中指定.RamFunc段到RAM(后面有详细解释)。- 动态修改代码:如测试快速算法迭代,避免频繁擦写Flash。
- Flash擦写期间:开发Bootloader时,需在RAM中运行擦写Flash的代码(Flash不能同时读写自身)。
默认情况下,代码在Flash中直接执行,无需拷贝到RAM。
只有数据段(.data)和特殊代码(RamFunc)需要拷贝到RAM。 通过合理配置链接脚本和启动文件,可以优化性能和内存使用。
5.2 为什么代码不全部拷贝到RAM?
- 原因
- RAM容量有限:STM32F407的Flash为1024KB,而RAM仅128KB(+64KB CCMRAM),无法容纳全部代码。
- 性能权衡:Flash的读取速度虽慢,但Cortex-M内核有预取缓冲(Prefetch Buffer)和指令缓存(I-Cache,仅F7/H7支持),可减少性能损失。
5.4 小试牛刀:将中断回调函数拷贝到RAM中运行
- 将函数拷贝到RAM中运行的场景:
- 执行Flash擦写操作时(Flash不能同时读写)。
- 对延迟极敏感的中断服务函数(如高频PWM控制)。
5.4.1 修改ld文件
/* Sections */
SECTIONS
{
//其他的段定义...
.RamFunc :
{
*(.RamFunc*)
} >RAM AT> FLASH /* 物理存储在Flash,运行时拷贝到RAM */
}
5.4.2 修改函数定义
在原本的中断回调函数前面加上__attribute__((section(".RamFunc"))),之后就可以了。
__attribute__((section(".RamFunc"))) void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
//这里的代码省略
}
5.4.3 测试结果
5.4.3.1 查看map文件
编译之后,查看map文件,我们得到如下结果:
*(.RamFunc)
.RamFunc 0x200003b0 0x108 ./Core/Src/main.o
0x200003b0 HAL_GPIO_EXTI_Callback
.RamFunc.__stub
0x200004b8 0x38 linker stubs
*(.RamFunc*)
0x200004f0 . = ALIGN (0x4)
0x200004f0 _edata = .
看到函数的定义在RAM的地址范围,那么就是证明了函数在RAM中。
5.4.3.2 debug测试
下图中,程序运行到中断处理函数的时候,从反汇编窗口看出来函数的地址是RAM空间的地址!于是我们断定:中断函数是被拷贝到了RAM中运行的。

5.4.4 记录一次掉坑的经历
在修改ld文件的时候,我将新增RamFunc 段的代码写成了下面的样子:
/* Sections */
SECTIONS
{
//其他的段定义...
.RamFunc:
{
*(.RamFunc*)
} >RAM AT> FLASH /* 物理存储在Flash,运行时拷贝到RAM */
}
怎么样?对比一下5.4.1章节的代码,有看出来不同吗?我估计绝大多数人看不出来!
我公布答案,就是.RamFunc和:之间少了空格!这谁能知道问题?编译的时候发现报错,就很抓狂。都是血与泪的教训啊。
至此,本文结束,耗时一整天,如果有对你有帮助,欢迎关注、点赞、收藏、转发!
6229

被折叠的 条评论
为什么被折叠?



