C语言解释器Java版-1-内存分配
来源:互联网 发布:facebook总是网络错误 编辑:程序博客网 时间:2024/05/29 14:33
1 内存分配
基本了解了内存模型后,接下来考虑在实现解释器时如何进行内存分配,即怎样将每个内存区进行数据分配和释放。
在进行具体内存分配前,需要定义输入接口:被解释的源程序 file.c 和入口函数 func,然后根据输入生成对应的AST、控制流程图和符号表。有了这些原始数据后,开始进行内存分配。
1.1 全局数据分配
这里的全局数据包括如下几类,每一类的分配都不尽相同。主要操作的内存区是代码段区和数据区。
1.1.1 函数内存分配
被解释程序file.c中的所有已定义函数,不包含extern的外部函数和 没有声明定义的函数。函数对应的内存段区是代码段区。在标准的C语言内存分配中,代码段区存放的是函数对应的二进制代码。
在我们的实现中,每一个函数在代码段区分配一个四字节的内存空间,内存地址递增。由于每个函数占用的内存大小相同,都是4个字节,所以这里不需要考虑内存对齐问题。(代码段区起始地址应该能被4整除)
1.1.2 字符串常量
在C语言中,字符串常量一般是一个char*的定义:
- 在分配内存时无法从符号表中通过符号类型获取长度;
- 为了统一处理指针类型(使用四个字节地址表示),char*被分配为一个四字节的内存地址,char*的值则分配到一块固定内存范围(即字符串常量区);
- 这样分配的另一个好处是,对于同样的字符串,只需要从指定的内存地址获取字符串的值即可,不需要另外开辟一段空间去存放字符串的值,能够节省空间。
另外,字符串常量区不需要考虑内存对齐,每一个字符都是一个char,所有不用考虑内存对齐问题。
1.1.3 全局定义变量
包含初始化和未初始化的变量。虽然在标准的定义中,区分了初始化和未初始化,但是,为了实现简便,我们的实现中做了合并处理。
- 对于每一个非指针类型的全局变量,按照大小分配指定的空间即可;
- 对于类型为指针的全局变量,需要分配的是4字节的地址空间,对于指向的内容,会在使用的时候进行指向处理。
- 每个全局变量占用的内存并不相同,所以,在进行内存分配时,需要考虑内存对齐。但是,在查阅相关文档时,并未获得有关全局变量在分配时如何进行内存对齐处理。所以,我们采用的方案是:分配的内存起始地址满足当前变量的对齐要求即可。
1.1.4 静态变量
包含全局定义和局部定义的静态变量。
- 静态变量的地址分配和全局变量没有区别;
- 在全局数据分配时,需要分配局部静态变量,所以需要遍历每一个函数,并找到该函数中定义的局部静态变量,并对其分配内存;
- 局部静态变量是需要关注初始化问题的。对于一个局部静态变量,第一次调用变量所属函数和第二次调用的值是不同的。有如下代码,第一次调用时sv的值是1,第二次调用时sv的值就是2….所以在第二次调用时,就需要注意,sv的赋值语句已经不起作用了。
void f(){ static int sv = 1; printf("%d",sv); sv++;}
1.2 局部数据分配
局部数据分配,主要操作的内存区是栈区和堆区。
- 在进入函数时,需要立即进行局部变量的内存分配。分配的局部变量包括:函数形参、每一个作用域的局部变量。
- 内存分配原则是,先参数,后变量,参数从右向左,变量按定义顺序。如下所示代码,分配内存的顺序是:b->a->c1->d1->c2->d2。
- 需要注意,在一进入函数f后,就需要将c2和d2的内存进行分配。这样对于永远不可能走到的分支,可能会无效分配很多内存,但是这是VC、GCC等编译器的共同选择。这样做可以解决的一个问题是,函数中的跳转语句或者其他循环语句,只分配一次内存,且可以解决因为这些语句引起的找不到内存、内存未分配等问题。
int f(int a,int b){ int c1,d1; //.... if(c1 + c2 > 0){ int c2,d2; //.... } return 1;}
- 进入函数时分配内存,意味着,退出函数时,需要释放内存。由于这些内存都是在栈中分配的,所以进入函数时,需要创建一个新的栈空间,存放函数中的局部变量,退出函数时,退出该栈空间(栈帧,stackFrame)即可。
- 栈区的内存分配是从高地址向地址进行分配的,也就是b比a的地址高。但是要注意,从高到低分配并不意味着起始地址就低。对于变量b,如果起始地址是0x70000004,则b的最大地址位置是0x70000008,而不是0x70000000,a的起始地址是0x70000000,最大地址是0x70000004。
- 堆区的内存分配是从低地址向高地址进行的。但是堆区的地址分配释放策略有很多种,不同的策略会导致分配的地址差异。对于堆内存的释放和分配策略,这里不多做说明,如果后续有需要,会单独进行说明。
1.3 函数调用
在解释执行函数调用语句时,需要先保存当前栈空间,然后切换到新的函数,创建新的栈空间,当新函数执行完成后,退出新的栈空间,进行栈还原,并执行下一条语句。这里,将每一个函数的一个栈空间称为一个栈帧(stackFrame)。函数调用的栈帧组成如下图所示:
- 每个frame由局部变量和参数、返回地址构成。在图中,main函数调用f1函数时,main的stackframe中保存了f1函数的实参,而f1函数的stackFrame中保存了main函数下一条语句的地址,也就是f1函数的返回地址。
- 当main函数开始调用f1时,先将调用的实参放入到mainframe,创建新的f1 stackFrame,将main函数调用完成f1后的下一条语句地址放入f1 stackFrame中。
- f1 stackFrame的其他创建工作还包括局部变量的分配等。在实际解释执行时,内存分配的同时,需要对参数进行赋值。这时候会读取mainframe中的实参值对f1的形参赋值。需要注意,这里的赋值也可能发生隐式类型转化。
- 函数调用的另外一个问题是可变参数的问题。在我们目前实现中,对于可变参数是直接忽略掉了。如果后续有需求,对可变参数会重新进行说明。
0 0
- C语言解释器Java版-1-内存分配
- C语言解释器Java版-0-内存模型
- C语言解释器Java版-3-内存值管理
- C语言解释器Java版-4-内存值转化
- C语言内存分配
- C语言内存分配
- c语言内存分配
- C语言-内存分配
- C语言内存分配
- C语言内存分配
- c语言内存分配
- C语言内存分配
- C 语言内存分配
- C语言内存分配
- C语言内存分配
- c语言内存分配
- c语言内存分配
- C语言内存分配
- 含空格的字符串的输入
- Hibernate和Spring 缓存的二十二问,干货分享
- git从本地仓库上传到github
- OC-2方法
- 【数据结构与算法】 哈夫曼树——哈夫曼编码的一个实例
- C语言解释器Java版-1-内存分配
- Codevs_P1080 线段树练习(线段树)
- glfw开发步骤
- Android自带的Theme主题图解
- python模块
- 如何用JDom读取XML文档
- Ubuntu下编译lua源码
- bs(二)导航条
- 求最长连续子序列之和