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

链接器脚本的基础结构
链接器脚本主要由内存区域定义、段分配、符号定义三个部分组成,核心作用是描述目标板的内存布局,以及将输入文件中的各个段映射到输出文件的内存区域中。
内存区域定义
首先需要用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