9.1 程序内存布局
编译器后端不是在“抽象空间”里发射指令。每一条寻址、每一次加载/存储(load/store)、每一种调用序列,都默认了一个运行时内存模型:代码放哪、全局数据放哪、栈帧如何生长、动态对象如何分配。
工程上常先用四个区域建立直觉:
- 代码段(text)存放指令
- 全局/静态区存放进程期数据
- 栈区存放调用期活动记录
- 堆区存放动态管理对象
不同平台会有不同细节,但这个划分已经足够支撑编译器实现中的大部分正确性讨论。
正在加载交互实验...
正在加载概念检查...
正在加载本节练习...
为什么后端必须关心它
前端主要处理语法、名字和类型。后端要把这些事实变成真实地址和可执行动作。
- 局部变量要落到寄存器或栈槽
- 静态对象要落到数据段并通过符号重定位访问
- 堆对象要通过运行时分配协议创建
所以内存布局会直接影响:
- 指令选择与寻址模式
- ABI 与跨语言互操作
- 分配策略与逃逸分析
- 调试信息与回溯可用性
正在加载交互实验...
正在加载概念检查...
正在加载本节练习...
真正的分类轴:生命周期
初学者常按“值长什么样”分类,但工程上更可靠的是问:谁管理它、活多久。
- 小对象也可能在堆上,只要它逃逸当前函数
- 数组也可能在栈上,只要生命周期受帧控制
- 字符串字面量通常是静态只读数据,不是堆对象
因此编译器工程里最关键的问题是:该存储在什么时点仍然有效,谁负责它的失效/回收。
边界例子
return &localVar 在语法和很多类型系统层面都“像是合法”,但运行时语义错误:函数返回后栈帧弹出,地址悬垂。这个错误只能靠内存布局与生命周期模型看出来。
这也是为什么运行时环境章节不只是系统课内容,而是编译器正确性的一部分。
正在加载概念检查...