简体   繁体   English

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

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

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

A function calls another function and chain goes on. 一个函数调用另一个函数,链继续进行。

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

When a function is called a structure named Activation record is prepared that contains the information associated with it's call. 调用一个函数时,将准备一个名为“ 激活记录 ”的结构,其中包含与调用相关的信息。 That activation record regarding each function is pushed on to a stack called program-stack or run-time-stack . 与每个功能有关的激活记录被推到称为program-stackrun-time-stack的堆栈上 I have no good idea either about the Activation record or Run-time-stack . 我对激活记录运行时堆栈都不了解。 My problem is : 我的问题是:

  1. A function calls another function and chain goes on. 一个函数调用另一个函数,链继续进行。 But for how long? 但是要持续多久? how long this nested calls can go on? 此嵌套通话可以进行多长时间? And What do they depend on? 而且他们依赖什么? Is it OS or bit architecture of the machine ? 操作系统还是机器的位架构

  2. What does Activation Record contain? 激活记录包含什么? What is its structure like? 它的结构是什么样的? Is local data also passed to this? 本地数据也传递给它吗?

  3. What parameters are set for stack overflow of such type of functions(which have several nested calls or recursive functions) ? 为此类函数(具有多个嵌套调用或递归函数)的堆栈溢出设置哪些参数? I mean how to know in advance to avoid overflow . 我的意思是要提前知道如何避免溢出

An "activation record" is an abstract concept, useful for academics performing formal analysis of algorithms and not too concerned by the actual implementation. “激活记录”是一个抽象的概念,对于学者进行算法的形式分析并且对实际实现不太关心的学者很有用。

Real systems have function arguments, local variables, saved register values, return addresses, etc. Some systems use CPU registers for some of all of this information. 实际系统具有函数参数,局部变量,保存的寄存器值,返回地址等。某些系统将CPU寄存器用于所有这些信息。

As the stack pointer is often one of the registers saved, in many environments one can "walk" the stack by using a frame pointer register to locate the saved value of the stack pointer and frame pointer as it existed in the caller, repeating as necessary. 由于堆栈指针通常是保存的寄存器之一,因此在许多环境中,可以通过使用帧指针寄存器来定位堆栈指针和帧指针在调用者中存在的已保存值,从而“遍历”堆栈,并根据需要重复进行。 If a compiler setting such as "frame pointer omission" is activated (or the system doesn't use a frame pointer register), this can instead be done using the stack pointer combined with debug metadata. 如果激活了诸如“框架指针省略”之类的编译器设置(或系统未使用框架指针寄存器),则可以使用堆栈指针与调试元数据结合来完成。

If you're writing such a stack walking algorithm, or computing total stack usage, you need to be concerned with stack layout. 如果要编写这样的堆栈遍历算法或计算堆栈的总使用量,则需要考虑堆栈布局。 It also is useful in understanding how a buffer overflow in a stack variable can lead to overwriting the return address in a "return to libc" exploit. 在理解堆栈变量中的缓冲区溢出如何导致“返回libc”漏洞中覆盖返回地址时,它也很有用。

But for most purposes, like explaining how recursive functions have multiple instances of local variables, or lifetime of aliases (pointers and references) to local variables, the conceptual model of a stack of activation records is sufficient. 但是对于大多数目的而言,例如解释递归函数如何具有局部变量的多个实例,或局部变量的别名(指针和引用)的生存期,激活记录堆栈的概念模型就足够了。

If your function creates local variables on the stack you will see the address of that variable change as the stack gets deeper. 如果您的函数在堆栈上创建了局部变量,则随着堆栈越深,您将看到该变量的地址发生变化。 You could get a rough idea of how much of the stack you have used up by keeping track of that number as it changes. 通过跟踪该数字的变化,您可以大致了解已用完多少堆栈。 Do this for your education - not in actual code. 这样做是为了您的教育-而不是实际的代码。 Something like this: 像这样:

#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;
}

On my machine it seems that the stack grows by 48 bytes for each subsequent call to foo() . 在我的机器上,每次后续调用foo() ,堆栈都会增加48个字节。

"Activation record" is synonymous with "stack frame" - I had to look this up as I'm not familiar with that term. “激活记录”是“堆栈框架”的同义词-我不熟悉该术语,因此必须查找它。

  1. In most systems, the stack begins with the maximum memory address and then grows down. 在大多数系统中,堆栈从最大内存地址开始,然后向下扩展。 A certain amount of memory is allocated for the stack (and a stack is created for each thread). 一定数量的内存分配给堆栈(并为每个线程创建一个堆栈)。 Frames can be added to the stack until it hits the limit and then it becomes a stack-overflow. 可以将帧添加到堆栈中,直到达到极限,然后变为堆栈溢出。 This generally only happens when a recursive function calls itself too many times - it is incredibly rare for a non-recursive call to cause a stack-overflow (one possibility is a function allocates a large amount of memory on the stack). 通常仅在递归函数调用其自身次数过多时才会发生这种情况-非递归调用很少引起堆栈溢出(一种可能性是函数在堆栈上分配大量内存)。
  2. The contents of a stack frame are detailed on wikipedia ( http://en.wikipedia.org/wiki/Call_stack ) - generally it contains the function call's return address and space for each stack-allocated (aka "local" or "automatic") variable. 堆栈框架的内容在Wikipedia( http://en.wikipedia.org/wiki/Call_stack )上进行了详细介绍-通常,它包含函数调用的返回地址和每个已分配堆栈的空间(又称“本地”或“自动”) )变量。
  3. Generally there is no way for a program to inspect its own call-stacks at runtime. 通常,程序无法在运行时检查其自己的调用堆栈。 The only way to avoid a stack-overflow is to keep track of the depth count yourself like so: 避免堆栈溢出的唯一方法是像这样跟踪自己的深度:

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