繁体   English   中英

在堆栈上进行“函数调用”的工作?

[英]Working of “function calls” on stack?

main() calling f1(), f1() calling f2(), f2 calling f3(), f3() calling f4() and so on...

一个函数调用另一个函数,链继续进行。

|        |
| f4()   |
| f3()   |
| f2()   |
| f1()   |
| main() |
__________

调用一个函数时,将准备一个名为“ 激活记录 ”的结构,其中包含与调用相关的信息。 与每个功能有关的激活记录被推到称为program-stackrun-time-stack的堆栈上 我对激活记录运行时堆栈都不了解。 我的问题是:

  1. 一个函数调用另一个函数,链继续进行。 但是要持续多久? 此嵌套通话可以进行多长时间? 而且他们依赖什么? 操作系统还是机器的位架构

  2. 激活记录包含什么? 它的结构是什么样的? 本地数据也传递给它吗?

  3. 为此类函数(具有多个嵌套调用或递归函数)的堆栈溢出设置哪些参数? 我的意思是要提前知道如何避免溢出

“激活记录”是一个抽象的概念,对于学者进行算法的形式分析并且对实际实现不太关心的学者很有用。

实际系统具有函数参数,局部变量,保存的寄存器值,返回地址等。某些系统将CPU寄存器用于所有这些信息。

由于堆栈指针通常是保存的寄存器之一,因此在许多环境中,可以通过使用帧指针寄存器来定位堆栈指针和帧指针在调用者中存在的已保存值,从而“遍历”堆栈,并根据需要重复进行。 如果激活了诸如“框架指针省略”之类的编译器设置(或系统未使用框架指针寄存器),则可以使用堆栈指针与调试元数据结合来完成。

如果要编写这样的堆栈遍历算法或计算堆栈的总使用量,则需要考虑堆栈布局。 在理解堆栈变量中的缓冲区溢出如何导致“返回libc”漏洞中覆盖返回地址时,它也很有用。

但是对于大多数目的而言,例如解释递归函数如何具有局部变量的多个实例,或局部变量的别名(指针和引用)的生存期,激活记录堆栈的概念模型就足够了。

如果您的函数在堆栈上创建了局部变量,则随着堆栈越深,您将看到该变量的地址发生变化。 通过跟踪该数字的变化,您可以大致了解已用完多少堆栈。 这样做是为了您的教育-而不是实际的代码。 像这样:

#include <stdio.h>

int foo(void);
char *sp;

int main(void){
  char a;
  sp = &a;
  foo();
  return 0;
}

int foo(void) {
  char c;
  long depth;
  depth = sp - &c;
  if(depth < 1000) {
    printf("depth is %ld\n", depth);
    foo();
  }
  return 0;
}

在我的机器上,每次后续调用foo() ,堆栈都会增加48个字节。

“激活记录”是“堆栈框架”的同义词-我不熟悉该术语,因此必须查找它。

  1. 在大多数系统中,堆栈从最大内存地址开始,然后向下扩展。 一定数量的内存分配给堆栈(并为每个线程创建一个堆栈)。 可以将帧添加到堆栈中,直到达到极限,然后变为堆栈溢出。 通常仅在递归函数调用其自身次数过多时才会发生这种情况-非递归调用很少引起堆栈溢出(一种可能性是函数在堆栈上分配大量内存)。
  2. 堆栈框架的内容在Wikipedia( http://en.wikipedia.org/wiki/Call_stack )上进行了详细介绍-通常,它包含函数调用的返回地址和每个已分配堆栈的空间(又称“本地”或“自动”) )变量。
  3. 通常,程序无法在运行时检查其自己的调用堆栈。 避免堆栈溢出的唯一方法是像这样跟踪自己的深度:

function beginFoo() {

    foo(0);
}

function foo(int depth) {
    if( depth > 10 ) return; // avert possible stack-overflow
    foo(depth + 1);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM