
深刻理解内存布局从虚拟地址空间到单片机的 Flash 与 RAM内存分段 / ASLR / 启动文件 / 链接脚本目录深刻理解内存布局从虚拟地址空间到单片机的 Flash 与 RAM内存分段 / ASLR / 启动文件 / 链接脚本学习内容一、从三个 bug 说起为什么必须懂内存布局二、PC 端虚拟地址空间分段总览三、栈 vs 堆取舍与性能视角四、ASLR教科书模型的现代演变PC 篇最有价值的一段五、嵌入式落地同一张地图落到 Flash 和 RAM六、上电之后向量表、启动文件、链接脚本1. 上电复位CPU 只做两件事Cortex-M2. 启动文件main 之前的三步铺垫3. 链接脚本启动文件的后台4. 八步完整链路全片收束七、收尾一张地图的两种投影学习内容一、从三个 bug 说起为什么必须懂内存布局《植物大战僵尸》的三个经典问题每个都指向一块内存区域后期卡顿→ 玩了很久后堆内存碎片化分配效率低、CPU 读数据不连贯放技能闪退→ 递归没有收敛条件 →栈溢出栈帧撑爆了有限的栈空间数值难调→ 数值硬编码在代码里 → 改成数据驱动逻辑代码住代码段数值从配置文件读进堆——策划改配置不用重新编译。这就是数据驱动的内存本质让每种数据住到它该住的段里。二、PC 端虚拟地址空间分段总览高地址 ┌──────────┐ │ 栈 │ 局部变量、函数调用帧向下生长 │ ↓↓↓ │ │ 隔离带│ │ ↑↑↑ │ │ 堆 │ malloc / new向上生长 ├──────────┤ │ BSS │ 未初始化 / 零值全局变量运行时清零 │ .data │ 初始值非零的全局 / 静态变量 │ .rodata │ const 变量、字符串字面量只读 │ .text │ 机器码只读 低地址 └──────────┘区域分段存放内容读写属性代码段.text机器码只读静态数据区.rodataconst 变量、字符串字面量只读改了就崩^.data初始值非零的全局/静态变量可读写^BSS未初始化或零值的变量可读写运行时清零动态数据区堆malloc / new 手动管理运行时分配忘了 free 就是泄漏^栈局部变量、函数调用编译器自动管理LIFO两个容易被忽略的细节带 “.” 前缀的段macOS 是双下划线__ 编译链接期就确定的可执行文件具名段堆和栈是纯运行时区域在可执行文件里没有对应的具名段BSS 的省磁盘设计零值不写进可执行文件只记元信息大小加载时预留内存并清零——int big[1000000];这种大数组场景可执行文件体积差距巨大。macOS 上非静态全局变量进__common、静态的进__bss两者都算 BSS 范畴。视频的验证方法objdump 反汇编 程序里打印各变量的地址逐段对号入座。三、栈 vs 堆取舍与性能视角栈堆管理编译器自动程序员手动速度极快相对慢泄漏绝不泄漏忘记 free 泄漏空间小大而灵活生命周期锁死在作用域内自己决定性能视角连续内存如vector遍历比链表快——CPU 缓存按目标 邻居整块加载连续 缓存命中分散 缓存缺失。这也是为什么面试总问数组 vs 链表的底层答案。四、ASLR教科书模型的现代演变PC 篇最有价值的一段实验发现每次运行程序堆和栈的基地址都不一样——这不是玄学是操作系统的ASLR地址空间布局随机化防攻击机制。但 ASLR并非完全无序实测规律仍然成立静态区地址永远最小栈永远在堆之上。为什么回到 32 位时代解释32 位虚拟空间只有 4GB必须让堆和栈相向生长来最大化中间余量——即便如此还有相撞风险64 位空间近乎无限操作系统改为在几个宏观范围内分别随机化堆和栈之间隔着巨大的安全隔离带永远不会相遇。结论老教科书模型不是错的——设计哲学从省空间演进成了以空间换安全 简化管理。五、嵌入式落地同一张地图落到 Flash 和 RAM到了单片机上没有虚拟地址空间同一张地图要物理落地到两块存储特性FlashRAM掉电不丢丢读写读快写慢读写快存放代码、只读常量、中断向量表、全局变量的初始值运行时变量、未初始化变量、栈和堆C 代码各归各位编译器 链接器自动分配C 代码段落在哪函数.textFlashconst 常量、字符串.rodataFlash有初始值的全局变量.data最特殊初始值存 Flash、运行体在 RAM启动时由启动文件从 Flash 复制到 RAM未初始化的全局变量.bss固件里只记范围不存实体省 Flash 空间启动时由启动文件清零局部变量寄存器或栈栈在 RAM注意 .data 和 .bss 的呼应.data 是初始值在 Flash、运行体在 RAM.bss 是只记账、不占 Flash——和 PC 端 BSS 省磁盘的设计一脉相承。六、上电之后向量表、启动文件、链接脚本1. 上电复位CPU 只做两件事Cortex-M从中断向量表第 0 项取初始栈顶地址 → 装进SPMSP从第 1 项取Reset_Handler地址 → 跳过去执行。向量表本身就在Flash 起始区域——它既管中断来了找谁也管栈从哪开始、复位后从哪执行。2. 启动文件main 之前的三步铺垫复制 .dataFlash → RAM→ 清零 .bss → 系统初始化时钟、C 运行环境、全局构造函数→ 才进 mainmain 不是程序的第一行——在它之前启动文件已经默默干了三件事。3. 链接脚本启动文件的后台启动文件只管拿地址干活地址是链接脚本安排的MEMORY块声明硬件地图FLASH 0x08000000 / 512K、RAM 0x20000000 / 128KRAM AT FLASH语法实现 .data 的两份存放VMA运行地址在 RAM加载地址LMA在 Flash导出符号给启动文件用_estack ORIGIN(RAM) LENGTH(RAM) 0x20020000——正好就是向量表第 0 项那个初始栈顶。4. 八步完整链路全片收束写 C → ① 编译成 .o分段但无最终地址→ ② 链接器按链接脚本安排地址 → ③ ELF → ④ bin/hex → ⑤ 烧录 Flash → ⑥ 复位取 SP 和 Reset_Handler → ⑦ 启动文件复制 .data / 清零 .bss→ ⑧ 调用SystemInit()初始化时钟 → ⑧ 进 main七、收尾一张地图的两种投影两期视频其实是同一张内存地图的两种投影PC 篇讲虚拟地址空间长什么样分段 ASLR嵌入式篇讲这张地图怎么物理落地到 Flash/RAM、上电后怎么一步步跑起来。贯穿两篇的也是同一个工程哲学BSS 省空间、32→64 位从省空间到以空间换安全、.data 两份存放换启动正确——内存布局从来不是背诵的表格而是在约束下做取舍的结果。