附加题二号
STACK和HEAP在英语中的意思大致都是堆,但翻译过来后加以区分为堆和栈。
首先在最初的数据结构课中我们就已经了解了栈和队列这两种较为简单的数据结构。STACK即FILO表,先进后出。栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。
然后在编译的学习里我们了解了调用栈,在这里我来简要对调用栈和系统栈区做一个区分,实现了子程序的调用,即把局部变量存在该层调用栈区,全局变量放在。以类C语言为例,在MIPS架构上编译出的程序:
Mips中有三个特殊的指针型通用寄存器
$fp,$sp,$gp.
$fp即帧指针,用来定位DISPLAY区,在类C语言中由于不存在函数嵌套,$fp可有可无,在某些时候会作为全局寄存器$S8优化汇编程序。
MIPS中,访问Memory的指令格式为:
lw r1, offset(r2) //offset只有16位
因此,为了存取全局变量以及静态变量,在$gp初始化后,可以通过$gp来访问静态变量,而这一部分就是前文中所述的静态区。静态区有限,必须在程序运行之前首先制定好大小,并且对每一个程序来说是不可更改的,如是,C语言的数组不可改变长度的这个设计便是为了强制用户在一开始把数组所需区域规划好,以放在栈上进行访问。而对于堆空间的存取则是抛给了操作系统,因而操作系统可以很好的通过调度腾出空间,然而这一部分由于执行了操作系统的代码抛出了异常,对性能的损耗要比使用栈空间大。还是以数组作为一个引子,即便是运程序员使用了可更改数组长度的语言,最好还是提前先规划好哪些东西应该存在栈上哪些应该放在堆上。
而对于$sp,在我自己做的编译器中,其充当的是一个系统调用栈栈顶的职责。编译过程中,各项变量在编译过程中都记录的是相对于$sp的偏移。并且在$a$v型寄存器参数存不下的时候也会存放函数调用的参数以及返回值,另外$ra寄存器被新的调用地址占用时,系统栈上也会存放原先的函数返回地址。值得一提的是,对于main函数的返回地址其实是操作系统代码。在每个子程序中,子程序并不知道自己只是个子程序,他可以在自己的系统栈层里找到自己声明的一切,并通过$gp指针访问一些静态变量,也会通过malloc出的地址通过操作系统访问到自己堆上的变量。当然,对每个子程序而言相同的堆上的地址其实也完全不相同,这也是由操作系统来完成的。由此可见栈区在call stack中,但call stack中还有其他的东西诸如返回地址,display区等等。
有一个很常见的例子,我们即便在main函数中声明一个大数组会爆栈,但放在全局变量区却不见得会出问题,我认为这便是栈区对数据量的限制。另外,大规模的递归调用也会引发系统栈溢出,但系统里内置函数往往会换种写法利用堆区和静态区从而使得程序依然正常运行。