栈与调用惯例

1.栈

    栈被定义为一个特殊的容器,用户可以将数据压入栈中,也可以将栈中的数据弹出,而且要遵循先进后出(FILO)的原则。在计算机系统中,栈是具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。

    在经典的操作系统里,栈从高地址向低地址增长,在i386下,栈顶由esp寄存器进行定位。压栈使栈顶地址减小,弹出使栈顶地址增大。

image

栈在程序运行过程中具有举足轻重的地位。栈保存了一个函数调用所需要维护的信息,这常常被称为堆栈帧或活动记录。一般包括以下几个方面:

  • 函数的返回地址和参数。
  • 临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。
  • 保存的上下文:包括在函数调用前后需要保持不变的寄存器。

一个常见的活动记录如下图所示:

image

一个函数的活动记录由ebp和esp两个寄存器划定范围。esp寄存器始终指向栈的顶部,同时也就指向了当前函数的活动记录的顶部。ebp寄存器指向了函数活动记录的一个固定位置,被称为帧指针。固定不变的ebp可以用来定位函数活动记录中的各个数据。例如函数返回地址的地址为ebp+4,再往前是压入栈中的参数,地址分别是ebp+8,ebp+12等,视参数数量和大小而定。ebp所直接指向的值是调用该函数前ebp的值,这样在函数返回的时候,ebp可以通过读取这个值恢复到调用前的值。

在i386下函数调用的过程:

  • 把所有或一部分参数压入栈,如果有其他参数没有入栈,那么使用某些特定的寄存器传递。
  • 把当前指令的下一条指令的地址压入栈中。
  • 跳转到函数体执行。
  • push ebp。
  • mov ebp,esp:esp=ebp。
  • 【可选】sub esp,XXX:在 栈上分配XXX字节的临时空间。
  • 【可选】push XXX:如有必要,保存名为XXX寄存器。
  • 【可选】pop XXX:恢复保存过的寄存器。
  • mov ebp,esp:esp=ebp,恢复ESP同时回收局部变量空间。
  • pop ebp:从栈中恢复保存的ebp的值。
  • ret:从栈中取得返回地址,并跳转到该位置。

2.调用惯例

函数的调用方和被调用方对于函数如何调用必须有一个明确的约定,只有双方都遵守同样的约定,函数才能被正确的调用,这样的约定就称为调用惯例。

调用惯例包含以下几个方面的内容:

  • 函数参数的传递顺序和方式。顺序:从左向右或从右向左;方式:栈传递或寄存器传递。
  • 栈的维护方式。是指栈中数据弹出的过程,可以有函数的调用方来完成,也可以由函数本身完成。
  • 名字修饰策略。为了链接的时候对调用惯例进行区分,调用惯例要求对函数本身的名字进行修饰。

几种主要的调用惯例:

无标题

原文地址:https://www.cnblogs.com/hust-ghtao/p/4380306.html