前提说明
由于工作原因,需要实现一个Java版的C语言解释器。在实现的过程中,遇到了很多问题、学到了很多东西,特将实现过程记录下来,备忘的同时,也供后来人学习参考。
在实现之前,已有的内容包括:前端AST、符号表系统和控制流程图。已有的内容我在后文的描述中不会过多说明,所以,看完本系列文章也无法保证读者一定能够按照步骤实现一个Java版的C语言解释器。但是对于一个想要了解解释器或编译器的朋友来说,本系列文章或多或少还是有所帮助的。
Outline
本文主要讲解C语言解释器的内存模型。
0 内存模型
标准内存模型结构如下图所示:
内存地址从低到高依次分为 代码段区(text)、数据区(data)、堆区(heap)和栈区(stack)。其中,代码段区、数据区是程序实际执行前就确定大小的,堆区和栈区在程序执行过程中动态变化。
在我们的实现中,对每一个区都有大小限制,可能和标准的定义不太相同。每个区可以没有明确的定义界限,根据实际的大小也确定;也可以定义明确的区段界限。也即,内存地址区段的划分只是为了便于识别、记忆、学习或者读取、判断,并不一定是一成不变的。如果需要获取标准的定义,可以去查阅相关官方的文档和资料。
0.1 代码段区
代码段区用于存放要执行函数的二进制代码。内存地址从低向高进行分配。
因为我们已经有前端AST和控制流程图,为了做基于源码的解释执行,在代码段区,我们并没有放入函数的二进制代码,只是放入了函数的入口地址(在后面内存分配会详细说明)。
0.2 数据区
在标准的数据区模型中,数据区可以分为:
- 常量区,常量区用于存放常量值;
- 未初始化区(.bss),存放了未初始化的全局变量和静态变量;
- 初始化区(.data),存放初始化过的全局变量和静态变量。
其中,静态变量包括全局和局部静态变量。这些数据的释放在程序执行结束以后。内存地址从低向高进行分配。
在我们的实现中,初始化区和非初始化的区别意义并不大,所以合并了这两个区(C++内存模型也如做了合并),即数据区由常量区和非常量区构成。在常量区中,主要存放的内容是字符串的常量值。
0.3 堆区
堆区主要用于存放程序执行过程中,由动态内存分配函数分配的内容空间。分配(alloc)和释放(free)都由程序本身来完成。如果在程序中分配了没有释放空间,会导致内存泄露。内存地址从低向高进行分配。
而在Java的内存模型中,堆区的内存由GC进行管理,所以Java不需要程序员手动的调用释放内存的方法。
0.4 栈区
栈区用于存放局部非静态变量(静态变量存放在数据区)。当进入一个函数时,分配所有内存到栈区,函数退出时,释放该函数的所有内存。需要知道的是,栈区的内存分配是从高地址向低地址进行的。