2.2 内存管理
编程语言是逻辑实现的载体,也是与机器交互的媒介。而逻辑的本质实际是数据流的收集、转化变换和分发转移。在物理概念上,数据在存取、计算等变化过程中,都会引发机器硬件信号的相应变化。所以编程就是在制定硬件信号的变化规则。
用语言编写出的程序在运行期间,实施这种变化规则的核心资源是处理器,而内存则是数据转化时的核心载体。所以内存管理都是任何一门编程语言的核心特性,也是机制设计中最为重要的部分。
通常而言,系统的内存划分为以下几个区域:
·寄存器(Registers)。寄存器位于处理器的内部,是CPU可直接控制并进行存取的地方,但数量与容量都有限制,所以一般用于高频数据的缓存。该区主要由操作系统直接管理,用户的语言代码也没有直接的控制权。
·栈(Stack)。栈区的存取效率仅次于寄存器,一般是连续的空间,常通过地址指针实施控制。该区的大小在程序运行前就会被设定好,但受限于操作系统及编译器的设置。在程序运行期间,编译器控制着栈区,实现资源的自动分配与释放,用于存储函数参数、局部变量、对象引用等。由于函数的调用及返回会涉及栈区内容的现场恢复等内部操作,所以大量的函数嵌套有可能会因为频繁的栈操作带来性能问题。
·堆(Heap)。这是程序自己可控制的区域,也叫内存池。该区不受编译器的控制,占用多大或占用多久均可由开发者自行控制。不同的语言在堆的管理方式上会有所不同。在C/C++这类语言中,对堆内存的分配与释放需要使用显式的方式,由开发者自行控制。如果不能在恰当时候释放内存,很容易导致内存泄漏;而且若释放不存在的内存区,也会引发致命的内存操作错误。不过在有垃圾回收机制的语言中,包括Python、Julia等,内存分配与释放是自动进行的,所以开发者不用过多关注,可将精力集中在逻辑实现上。不过超出可用内存的数据占用也同样会出现溢出错误。
·静态(Static)区。一般该区用于存储静态变量或者全局变量,即那些不需要变动位置的数据。在一些语言中,可以显式地告知编译器哪些变量是全局的,哪些变量是静态的。但该区的直接控制权在编译器而不在开发者手中。
·常量(Constant)区。程序中经常会有一些值是始终不变的,例如物理或数学系数、配置文件路径等。将这些特殊值放在一个专门的常量区,不但可以保证数据的安全性也能够提高运行效率。不过,常量区一般是运行期不可变的区域,由编译器直接控制。
除了上述的这些内存区,还有扩展存储空间,例如磁盘、固态硬盘以及U盘等。在需要时,可以使用程序对这些非RAM存储介质中的内容进行存取操作,但效率肯定不及内存,所以通常是IO(Input/Output,输入输出)操作最为耗时的部分。
除此之外,内存管理的另外一个方面是垃圾回收(Garbage Collection,GC),这是不少现代语言包括Java、Python、Julia等都支持的机制。在该机制支持下,堆中对象的分配与释放并不需要开发者显式地操作,而由系统自动执行,这样能够大大提高开发的效率及程序的安全性。一般而言,在语言内部会对创建的对象建立引用计数机制,便于GC记录跟踪内存使用的情况。当一个对象不再被有效引用时,便将其标识为垃圾,会在恰当的时候进行回收。