一、整体前置逻辑
所有段(代码段 .text、数据段 .data/.bss、动态堆 .heap)的起始地址完全由链接脚本 .ld 决定;
芯片出厂固化 Flash/RAM 物理基地址,ld 在这个大空间内切分各段区间。
以常见 Cortex-M/RISC-V 物联网芯片举例:
/* 硬件固定物理基地址,芯片手册定义 */
FLASH : ORIGIN = 0x08000000, LENGTH = 1M /* 存放代码、常量 */
RAM : ORIGIN = 0x20000000, LENGTH = 512K /* 存放全局变量、栈、堆 */
二、代码段 .text 地址如何确定
1. 基础规则
.text 是程序二进制代码、函数指令存储段,放在 Flash 里。
1. 默认从 FLASH 分区起始地址开始排布;
2. ld 从上到下依次填入所有 .o 文件的函数指令,顺序由链接顺序决定;
3. 段起始地址 = FLASH 分区基地址(默认不偏移)。
示例 ld:
SECTIONS
{
.text :
{
*(.text*) /* 所有目标文件代码 */
*(.rodata*) /* 常量字符串、常量数组 */
} > FLASH /* 绑定到Flash分区 */
}
• 本段起始地址:0x08000000
• 代码越多,.text 占用长度越大,结束地址自动后移。
2. 手动指定偏移场景
如果需要预留升级头、校验区,可手动设置起始偏移:
.text ALIGN(16) :
{
. = 0x200; /* 跳过前0x200字节头部 */
*(.text*)
} > FLASH
此时代码段起始地址变为 0x08000200。
3. 业务改动影响
新增/删除函数、代码逻辑修改,只会改变 .text 占用长度,起始地址不变;
只有修改 ld 的偏移/分区基地址,才会改变代码段起始。
三、数据段 .data / .bss 地址如何确定
数据段存放在 RAM 中,分两类:
1. .data:初始化全局变量(int a = 10;),Flash存初始值,上电拷贝到RAM;
2. .bss:未初始化全局/静态变量(int b;),上电直接清零,不占用Flash。
分配流程
1. .text 在 Flash 占用完毕后,ld 会在 RAM 分区开辟一段连续空间给数据段;
2. 默认紧跟 RAM 起始地址,或者你手动划定独立 RAM_STATIC 分区;
RAM_STATIC : ORIGIN = 0x20000000, LENGTH = 200K
SECTIONS
{
.data : { *(.data*) } > RAM_STATIC
.bss : { *(.bss*) } > RAM_STATIC
}
• .data 起始地址 = 0x20000000;
• .bss 紧跟 .data 末尾,地址自动顺延。
业务改动影响
新增全局静态变量,只会拉长 .data/.bss 占用长度,段起始地址不变;
只要不修改 ld 的 RAM_STATIC 起始地址,数据段基地址固定。
四、动态堆 .heap 起始地址怎么确定(重点对应你的私有堆场景)
两种主流分配模式
模式1:统一全局堆(单固件通用)
堆放在 RAM 中,排布在 .bss、栈之后,规则:
1. RAM 中先放 .data + .bss;
2. 再分配栈 .stack;
3. 剩余连续空闲RAM全部划给 .heap;
SECTIONS
{
.data > RAM
.bss > RAM
.stack ALIGN(8) : { . += 8K; } > RAM
/* 堆紧跟栈末尾,起始地址 = 栈结束地址 */
.heap ALIGN(8) :
{
PROVIDE(_heap_start = .);
. += 128K;
PROVIDE(_heap_end = .);
} > RAM
}
• 堆起始地址 = 栈段结束地址;
• 缺点:业务新增全局变量、增大栈后,.bss/.stack 变长,堆起始地址会自动往后偏移。
模式2:独立分区私有堆(你双BIN内存隔离推荐)
手动在ld单独划分一块独立RAM分区,堆起始地址永久固定,不受data/bss/栈影响
/* 静态变量、栈独立区域 */
RAM_STATIC : ORIGIN = 0x20000000, LENGTH = 200K
/* 业务1私有堆,物理地址完全固定 */
RAM_HEAP1 : ORIGIN = 0x20032000, LENGTH = 200K
SECTIONS
{
.data .bss .stack > RAM_STATIC
/* 业务1堆段绑定独立分区 */
.heap1 ALIGN(8) :
{
PROVIDE(heap1_start = .);
. += LENGTH(RAM_HEAP1);
PROVIDE(heap1_end = .);
} > RAM_HEAP1
}
1. .heap1 起始地址永久等于 0x20032000;
2. 不管业务新增多少全局变量、栈调多大,只会占用 RAM_STATIC,堆起始地址完全不变;
3. 这就是双BIN内存隔离的核心优势,业务迭代不会改变堆地址。
关键区分
1. 不独立分区:改业务代码会扩张data/bss/栈,堆起始地址自动偏移;
2. 独立RAM分区:堆地址锁死,业务逻辑修改不影响堆布局。
五、总结对照表
段类型 存放介质 起始地址由谁决定 业务代码修改是否改变本段起始地址
.text 代码段 Flash ld FLASH分区基地址+手动偏移 不改变起始,仅变长占用
.data/.bss 数据段 RAM ld RAM_STATIC分区起始 不改变起始,仅变长占用
全局默认堆 .heap RAM data+bss+stack 总占用末尾 会偏移,业务变量越多堆起始越靠后
独立私有堆 .heapX RAM ld 单独RAM_HEAPX分区ORIGIN 完全不变,隔离架构专用
六、适配你双BIN项目的落地要点
1. 代码、数据段、私有堆全部使用独立分区写法,固化 ORIGIN 起始地址;
2. 业务迭代新增变量、函数、Mesh/WiFi/AI业务,只会占用静态区,堆地址无变动;
3. 私有堆管理器初始化直接读取 ld 导出的 heapX_start 符号,无需硬编码地址;
4. 搭配 MPU/PMP 硬件保护,各段地址边界硬件锁定,杜绝跨段内存踩踏。