c++嵌入式开发中如何使用链接器脚本控制内存布局

来源:PHP编程网作者:马来西亚程序员头衔:程序员
导读:本期聚焦于小伙伴创作的《c++嵌入式开发中如何使用链接器脚本控制内存布局》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《c++嵌入式开发中如何使用链接器脚本控制内存布局》有用,将其分享出去将是对创作者最好的鼓励。

在c++嵌入式开发中,芯片的存储资源通常分为Flash、RAM等不同区域,不同区域的访问速度、可擦写特性存在差异,需要把程序的不同部分放到合适的内存区域中,链接器脚本就是用来告诉链接器如何分配这些内存资源的配置文件。

c++嵌入式开发中如何使用链接器脚本控制内存布局

链接器脚本的基础结构

链接器脚本主要由内存区域定义、段分配、符号定义三个部分组成,核心作用是描述目标板的内存布局,以及将输入文件中的各个段映射到输出文件的内存区域中。

内存区域定义

首先需要用MEMORY命令定义目标板的内存区域,每个区域需要指定名称、起始地址、长度、属性。属性中r代表可读,w代表可写,x代表可执行,!代表不可访问。

/* 定义内存区域,假设芯片有64KB Flash和20KB RAM */
MEMORY
{
  FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 64K  /* Flash起始地址0x08000000,只读可执行,存放代码和只读数据 */
  RAM (rwx)   : ORIGIN = 0x20000000, LENGTH = 20K  /* RAM起始地址0x20000000,可读可写可执行,存放变量和栈堆 */
}

段分配规则

段分配通过SECTIONS命令实现,用来将输入文件中的段(如.text代码段、.data已初始化数据段、.bss未初始化数据段)映射到前面定义的内存区域中。

SECTIONS
{
  /* .text段:存放程序代码,映射到FLASH区域 */
  .text :
  {
    *(.text)          /* 所有输入文件的.text段 */
    *(.text.*)        /* 所有输入文件的.text子段 */
    *(.rodata)        /* 只读数据段,比如const修饰的变量 */
    *(.rodata.*)
    . = ALIGN(4);     /* 4字节对齐,提升访问效率 */
  } > FLASH

  /* .data段:存放已初始化的全局变量、静态变量,运行时在RAM,初始值在FLASH */
  .data :
  {
    _sdata = .;       /* 定义符号,标记.data段起始地址 */
    *(.data)
    *(.data.*)
    . = ALIGN(4);
    _edata = .;       /* 定义符号,标记.data段结束地址 */
  } > RAM AT> FLASH   /* 运行时在RAM,加载地址在FLASH */

  /* .bss段:存放未初始化的全局变量、静态变量,运行时在RAM,不需要初始值 */
  .bss :
  {
    _sbss = .;        /* 标记.bss段起始地址 */
    *(.bss)
    *(.bss.*)
    *(COMMON)         /* 存放未初始化的公共符号 */
    . = ALIGN(4);
    _ebss = .;        /* 标记.bss段结束地址 */
  } > RAM
}

c++嵌入式开发中的特殊注意事项

c++程序相比c程序会多出一些特殊的段,需要在链接器脚本中额外处理,否则可能导致程序运行异常。

处理c++的构造和析构段

c++的全局对象、静态对象会在main函数执行前调用构造函数,在程序退出时调用析构函数,这些构造和析构函数的地址会存放在.init_array.fini_array段中,需要把它们放到FLASH区域,并且在启动代码中处理这些段的调用。

SECTIONS
{
  /* 其他段定义省略 */

  /* 构造段:存放全局/静态对象的构造函数地址 */
  .init_array :
  {
    _sinit_array = .;
    KEEP(*(.init_array))   /* KEEP命令防止链接器优化掉这些段 */
    KEEP(*(.init_array.*))
    _einit_array = .;
  } > FLASH

  /* 析构段:存放全局/静态对象的析构函数地址 */
  .fini_array :
  {
    _sfini_array = .;
    KEEP(*(.fini_array))
    KEEP(*(.fini_array.*))
    _efini_array = .;
  } > FLASH
}

处理异常相关段

如果c++程序使用了异常处理机制,还需要处理.eh_frame.gcc_except_table等异常相关的段,通常把这些段也放到FLASH区域中。

SECTIONS
{
  /* 其他段定义省略 */

  /* 异常处理相关段 */
  .eh_frame :
  {
    KEEP(*(.eh_frame))
  } > FLASH

  .gcc_except_table :
  {
    KEEP(*(.gcc_except_table))
  } > FLASH
}

完整的链接器脚本示例

下面是一个适配stm32f103系列芯片的完整链接器脚本示例,覆盖了c++程序需要的所有段分配。

/* 定义内存区域 */
MEMORY
{
  FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 64K
  RAM (rwx)   : ORIGIN = 0x20000000, LENGTH = 20K
}

/* 段分配 */
SECTIONS
{
  /* 中断向量表,放到FLASH最开始的位置 */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector))
    . = ALIGN(4);
  } > FLASH

  /* 代码段和只读数据段 */
  .text :
  {
    . = ALIGN(4);
    *(.text)
    *(.text.*)
    *(.rodata)
    *(.rodata.*)
    *(.glue_7)
    *(.glue_7t)
    . = ALIGN(4);
  } > FLASH

  /* 构造段 */
  .init_array :
  {
    . = ALIGN(4);
    _sinit_array = .;
    KEEP(*(SORT(.init_array.*)))
    KEEP(*(.init_array))
    _einit_array = .;
    . = ALIGN(4);
  } > FLASH

  /* 析构段 */
  .fini_array :
  {
    . = ALIGN(4);
    _sfini_array = .;
    KEEP(*(SORT(.fini_array.*)))
    KEEP(*(.fini_array))
    _efini_array = .;
    . = ALIGN(4);
  } > FLASH

  /* 异常处理段 */
  .eh_frame :
  {
    KEEP(*(.eh_frame))
  } > FLASH

  /* 已初始化数据段 */
  .data :
  {
    . = ALIGN(4);
    _sdata = .;
    *(.data)
    *(.data.*)
    . = ALIGN(4);
    _edata = .;
  } > RAM AT> FLASH

  /* 未初始化数据段 */
  .bss :
  {
    . = ALIGN(4);
    _sbss = .;
    *(.bss)
    *(.bss.*)
    *(COMMON)
    . = ALIGN(4);
    _ebss = .;
  } > RAM

  /* 栈和堆的空间预留,由启动代码使用 */
  _estack = ORIGIN(RAM) + LENGTH(RAM);  /* 栈顶地址,RAM的最高地址 */
}

链接器脚本的使用方法

在编译c++嵌入式程序时,需要通过编译器的链接参数指定链接器脚本的路径,以gcc-arm-none-eabi工具链为例,编译命令如下:

# 编译c++源文件为对象文件
arm-none-eabi-g++ -c -mcpu=cortex-m3 -mthumb main.cpp -o main.o

# 链接对象文件,指定链接器脚本为stm32f103.ld
arm-none-eabi-g++ -mcpu=cortex-m3 -mthumb -Tstm32f103.ld main.o -o main.elf

# 将elf文件转换为bin文件,用于烧录到芯片
arm-none-eabi-objcopy -O binary main.elf main.bin

如果需要查看链接后的段分布是否符合预期,可以使用arm-none-eabi-objdump工具查看elf文件的段信息:

arm-none-eabi-objdump -h main.elf

输出的信息中会显示每个段的起始地址、大小、所在的内存区域,开发者可以根据这些信息验证链接器脚本的配置是否正确。

linker_scriptC++嵌入式开发内存布局修改时间:2026-07-21 02:03:36

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。