data,bss,全局变量,栈,堆内存分配机制

一、整体前置逻辑

所有段(代码段 .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 硬件保护,各段地址边界硬件锁定,杜绝跨段内存踩踏。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容