从编译到内存全揭秘!STM32工程师必看:点击Build后,代码到底经历了什么?

从编译到内存全揭秘!STM32工程师必看:点击Build后,代码到底经历了什么?

序言:你是否也被这些问题卡过壳?

点击STM32CubeIDE的“编译”按钮,看着进度条滚动时,你有没有好奇过:一行行C代码是如何变成单片机能执行的二进制文件的?为什么明明定义了变量到CCMRAM,map文件里却跑到了RAM?编译后的数据段、代码段到底藏在Flash还是RAM里?为什么有时候函数必须放到RAM里才能跑?

这些看似“底层”的问题,恰恰是嵌入式开发的“隐形门槛”——搞懂它们,你能轻松解决90%的编译报错、内存溢出、变量异常问题;不懂它们,调试时可能对着乱码的变量一脸懵,甚至在项目上线后踩坑。

这篇文章就带你扒开STM32编译的“黑箱”:从预编译→编译→汇编→链接的完整流程图解,到链接脚本里FLASH.ldRAM.ld的核心区别;从text/data/bss段的内存分布,到手把手教你把变量“钉死”在CCMRAM的指定地址,再到如何让高频中断函数在RAM里飞跑……

全程干货无废话,既有原理拆解,更有实战案例(附map文件验证截图+避坑指南)。无论你是刚入门的新手,还是想进阶的工程师,读完这篇都能打通“代码→内存”的任督二脉。建议点赞+收藏,下次遇到编译内存问题,直接翻出来对着调!

一、点击工具栏的 🔨 Build 按钮的时候发生了什么

STM32CubeIDE中, 我们编写完代码,点击“编译”按钮的时候,从C语言源代码到可执行文件经过哪几个步骤?

从源代码到可执行文件(.hex.bin)的生成会经历多个关键步骤,包括 预编译、编译、汇编、链接。以下是详细的流程解析:

1.1 整体编译流程

源代码
预编译 Preprocessing
编译 Compilation
汇编 Assembly
链接 Linking
可执行文件 .elf/.hex/.bin

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

以下是具体区别:

  1. 文件结构与编码方式
特性HEX 文件BIN 文件
格式文本格式(ASCII 编码)纯二进制格式(原始数据)
内容包含地址、数据、校验和等元信息仅包含原始二进制数据
可读性人类可读(可用文本编辑器查看)不可读(需专用工具解析)
文件大小较大(因包含额外信息)较小(仅存储有效数据)
  1. 存储内容对比
功能HEX 文件BIN 文件
地址信息包含(支持分段地址扩展)不包含(需指定烧录地址)
数据填充可跳过未使用的地址区域必须连续填充(全地址空间)
校验和每行包含校验和(防止传输错误)无校验和
扩展功能支持启动地址、结束标志等仅存储原始数据
  1. 典型应用场景
场景HEX 文件BIN 文件
调试/开发常用(IDE 默认生成,如 Keil、IAR)较少直接使用
生产烧录需烧录工具解析(如 J-Flash)直接烧录(地址需手动指定)
OTA 升级较少(因体积大)常用(节省带宽)
Bootloader需解析地址直接拷贝到指定地址
Q3:如何减少编译后的代码大小?
  1. 编译优化(-Os)。

在这里插入图片描述

  1. 移除未使用的函数(-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.ldSTM32F407ZGTX_FLASH.ld。它们是两个不同的 链接脚本(Linker Script),它们的核心区别在于 程序存储和运行的位置。以下是详细对比和原因分析:

  1. 核心区别
特性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)
  1. 为什么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 内存段分类
段名全称存储内容存放位置
CodeCode(代码段)程序代码(机器指令)、常量字符串、只读数据(如 const 变量)Flash
RORead-Only(只读数据)只读数据(如常量数组、const 全局变量)、部分编译器会将 Code 合并到 ROFlash
RWRead-Write(可读写数据)已初始化的全局变量、静态变量(如 int a = 10;Flash + RAM
ZIZero-Init(零初始化段)未初始化或显式初始化为 0 的全局变量/静态变量(如 int b;int c = 0;RAM
2.3.1.2 内存分布详解
  1. Flash(ROM)存储内容

    • Code + RO

      • 程序代码(函数、指令)。
      • 只读数据(如 const uint8_t table[] = {1, 2, 3};)。
    • RW 的初始值

      • 已初始化的全局变量(如 int flag = 1;)的初始值会存储在 Flash 中,程序启动时由启动代码(如 startup.s)拷贝到 RAM
  2. RAM 运行时占用

    • RW(运行时)
      • 从 Flash 加载初始值后的全局变量(占用 RAM)。
    • ZI
      • 未初始化的变量(默认填 0,由启动代码初始化)。
  3. 示例分析
    假设编译后输出如下信息(来自 Keil 或 GCC 的 map 文件):

    Program Size: 
      Code = 10240 bytes 
      RO   = 2048 bytes  
      RW   = 512 bytes   
      ZI   = 1024 bytes  
    
  4. 计算总占用

    • Flash 总占用 = Code + RO + RW(初始值) = 10240 + 2048 + 512 = 12800 字节
      (RW 的初始值需存储在 Flash 中,运行时拷贝到 RAM)。
    • RAM 总占用 = RW(运行时) + ZI = 512 + 1024 = 1536 字节

2.3.2 STM32CubeIDE编译后的信息详解

输出示例:

   text    data     bss     dec     hex filename
  10240     512    1024   11776    2e00 firmware.elf

注意:以上数据全部是十进制!

  • text = Code + RO
  • data = RW(初始值在 Flash,运行时在 RAM)
  • bss = ZI
  • dec = 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 数据段的处理

  1. 已初始化全局变量(.data段)

    • 存储:初始值保存在Flash中(编译时确定)。

    • 运行时:启动时由 startup_*.s 中的初始化代码将初始值从Flash拷贝到RAM。

      示例:

      1. 定义全局变量 int a = 10;
      2. 初始值 “10” 存储在Flash中。
      3. 上电后,启动代码将 “10” 拷贝到RAM的变量地址。
  2. 未初始化全局变量(.bss段)

    • 存储:无初始值(或默认为0)。
    • 运行时:启动代码将 .bss 段对应的RAM区域清零。
  3. 只读数据(.rodata段)

    • 存储:始终留在Flash中,无需拷贝到RAM。

3.4.2 STM32启动流程

STM32上电后,执行顺序如下:

  1. 硬件复位:PC指针跳转到Flash起始地址(0x08000000),执行 Reset_Handler
  2. 初始化.data段:将Flash中的初始值拷贝到RAM的 .data 段。
  3. 清零.bss段:将RAM中的 .bss 段全部置零。
  4. 设置堆栈指针:将 _estack(栈顶地址)赋值给SP寄存器。
  5. 跳转到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段的加载地址是0x08016b94Flash 区域),字符数组变量实际位于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:之间少了空格!这谁能知道问题?编译的时候发现报错,就很抓狂。都是血与泪的教训啊。


至此,本文结束,耗时一整天,如果有对你有帮助,欢迎关注、点赞、收藏、转发!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值