编译原理-内存部分
这一篇主要是对后端编译器对内存布局的分析和笔记,之前的有关词法/语法方面的理论知识实在是太枯燥了,翻了又翻是在是难以理解,只能暂时放下那一部分的内容了.
在编译时主要将内存划分为两个部分栈、堆:
栈主要是作为程序的临时空间实现方法、局部变量的区域
堆主要是作为分配大对象的区域
存储组织
存储组织指的是内存区域,字节是内存中的最小编址单元.一个字节包含8个bit.多个连续的字节作为一块内存区域,并这块区域的第一个字节作为它的地址
这里需要注意的是向操作系统申请的一段连续的内存空间可能并不是连续的,内存地址是操作系统提供的,在底层是通过页表的形式将物理上不连续的空间组织成为逻辑上连续的一段内存地址
数据对象的存储分配受到目标机器的寻址约束影响很大。在很多机器中,执行整数加法的指令可能要求整数必须是位数相同(对齐)的。也就是说这些对象长度必须被4整除(4字节)。编译器可能会对数据进行指针对齐操作,对于空白的空间被称为‘补白’
'指针对齐’其实是由于CPU决定的,当现代CPU决定以2的指数倍数进行数据读取计算时,就决定了数据-指针的位数了。'补白’的指针在进行运算时肯定是优与未进行’补白’的指针的
生成的目标代码在编译时刻就决定了编译后的大小了,因此编译器可以将可执行目标代码放到一个静态确定的区域:代码区
静态区域:
静态区域指的是在编译后就能确定内存分配的区域,例如代码区\JVM中的常量池等
动态区域:
动态区域指的是只能在运行时才能填充的内存区域,例如局部变量、堆区等
关于静态分配和动态分配分别表示的是编译时刻、运行时刻
动态分配主要有两种实现方式分别是
- 栈式存储
一个过程局部名字在栈中分配空间,通常支持过程调用/结果返回 - 堆存储
有一些数据结构的生命周期比较长,这些数据通常被放到一个可重复存储的"堆"中
为了支持堆区管理,通过设计"垃圾回收器"来是的在运行时刻能够检测出堆区的无用的数据结构,并且自动回收
GC的概念在很早的时候就出现了
栈空间
有些语言使用过程、函数或方法作为用户自定义动作的单元,通过在运行时刻按照栈结构进行管理。在调用一个过程的时候将存放这个过程的指针入栈,计算结束时出栈。这样能带来的好处是允许同时调用多个不交叠的过程
为了将运行时刻的空间最大化利用,堆和栈分别被放到内存空间的两端,栈区用来存放活动记录的数据结构,这些活动记录会在函数调用过程中产生.栈区存放在内存高位端向下增长,堆区存放在低位端向上增长.
静态与动态的区别
-
静态指的是编译器只需要观察程序源代码就可分析出某个存储的决定,这个时期被称为编译时刻
-
动态指的是需要在程序执行过程中才会对内存地址进行分配
动态存储分配
动态存储分配可以有两种实现方式:
1. 栈式存储
2. 堆存储
栈式存储的好处
栈式存储的好处是在于方便活跃时间段内不重叠的多个过程调用时共享空间,还有一个好处是保持非局部变量的地址不变
栈帧
在之前的理解中把栈帧理解称为一个局部变量帧空间与栈结构的联合体,这个理解是错误的,栈帧指的就是一个活动记录,只是栈中的一个元素而已
函数式语言
函数式语言支持将一个函数作为参数传递到方法中,也支持方法中直接返回函数.java中通过类似语法糖的方式实现了部分函数式的调用过程,并不是真正的函数式调用.java不能实现函数式调用的根本原因在于java无法在运行时刻更改栈空间的非成员变量的地址,在栈空间中的非成员变量地址已经确定了的.
堆管理
堆是存储空间中的一部分,它被用来存储那些生命周期比较长的对象.堆必须具备两个能力:
- 分配空间
- 回收空间
关于堆能操作的空间也只能是在程序初始化开始后向操作系统申请的空间,对于这段空间我们的堆处理程序认为是连续的,但是在物理结构上不一定是连续的,这一部分是操作系统屏蔽的内存分配的实现细节
GC这门技术发展的真的很早,很早.也是比较底层的技术,直到现在还有源源不断的新想法出现
计算机的存储结构
这一段与编译没有直接关系,但是我觉得很好,推荐出来
一个处理器一般都拥有几个专属的寄存器,寄存器的内容由CPU厂商提供接口供上层软件使用.在往上可以看到由一层或多层高速缓存,做开始