4. 使用const关键字将常量放入Flash

任务目标

在嵌入式C语言开发中,默认情况下字符串字面量和未加限定符的全局变量会占用RAM。本案例通过将只读数据定义为const,使其在编译后存放于Flash(代码存储区),从而减少RAM占用。完成本案例后,你将能够通过Map文件或链接脚本确认常量已从RAM迁移至Flash。

环境与依赖

  • 硬件:任意ARM Cortex-M系列开发板(如STM32F103C8T6)
  • 工具链:arm-none-eabi-gcc + Makefile(或使用STM32CubeIDE)
  • 调试器:ST-Link / J-Link(用于查看内存分布)

关键完整代码


/* main.c - 演示const数据存放于Flash而非RAM */
#include <stdio.h>
#include <stdint.h>

/* 非const全局变量:存放在RAM(.data段) */
uint32_t g_ram_counter = 100;

/* const全局变量:存放在Flash(.rodata段) */
const uint32_t g_flash_version = 0x01020304;

/* const字符串字面量:存放在Flash */
const char g_welcome_msg[] = "Hello from Flash!";

/* const数组:存放在Flash */
const uint8_t g_sine_table[16] = {
    0, 25, 50, 74, 98, 120, 141, 160,
    177, 191, 203, 212, 219, 223, 225, 225
};

int main(void)
{
    uint32_t sum = 0;
    
    /* 读取Flash中的常量 */
    sum += g_flash_version & 0xFF;
    sum += g_welcome_msg[0];
    sum += g_sine_table[7];
    
    /* 使用g_ram_counter避免编译器优化移除 */
    g_ram_counter += sum;
    
    while (1) {
        /* 空循环,保持程序运行 */
    }
    return 0;
}

编译与链接配置

使用以下链接脚本片段(或修改现有.ld文件),确保Flash区域包含只读数据段:


/* 在链接脚本中确认.rodata被放入Flash */
SECTIONS
{
    .text :
    {
        *(.text*)
        *(.rodata*)
        *(.rodata.*)
    } > FLASH

    .data :
    {
        *(.data*)
    } > RAM AT> FLASH

    .bss :
    {
        *(.bss*)
    } > RAM
}

运行与验证方法

  1. 编译:使用arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Tlink.ld main.c -o test.elf编译工程(根据实际芯片调整-mcpu参数)。
  2. 检查Map文件:编译时添加-Wl,-Map=output.map生成Map文件,然后执行:

grep -E "g_flash_version|g_welcome_msg|g_sine_table|g_ram_counter" output.map

预期输出(示例,地址因芯片而异):


.g_ram_counter     0x20000000    0x4
.g_flash_version   0x08000050    0x4
.g_welcome_msg     0x08000054    0x10
.g_sine_table      0x08000064    0x10

观察地址:g_ram_counter位于0x20000000(RAM起始),而三个const变量均位于0x08000000(Flash起始),证明常量已存入Flash。

  1. 查看编译输出:在编译日志中确认.rodata段大小。执行arm-none-eabi-size test.elf,观察text(含rodata)与data段大小差异。
  2. 通过条件:Map文件中所有const修饰的全局变量地址落在Flash区域(如0x08000000~0x0801FFFF),且g_ram_counter地址在RAM区域(如0x20000000~0x2000FFFF),即判定通过。

常见问题

  • const变量仍出现在RAM中:检查是否在函数内部定义了非静态const局部变量——这类变量若未初始化会存放在栈上;若初始化则可能被编译器复制到RAM。确保使用static const或文件作用域const。
  • 编译器优化掉未使用的const:若const变量未被引用,链接器可能将其丢弃。本案例中通过sum累加引用了所有常量,避免被优化。
  • 字符串字面量:直接使用"string"字面量时,其存储位置由编译器决定(通常已在Flash)。显式定义为const char[]可确保存放于.rodata段。