main() calling f1(), f1() calling f2(), f2 calling f3(), f3() calling f4() and so on...
一个函数调用另一个函数并且链继续。
| |
| f4() |
| f3() |
| f2() |
| f1() |
| main() |
__________
当调用某个函数时,会准备一个名为激活记录的结构,其中包含与其调用相关的信息。关于每个函数的激活记录被推送到名为程序堆栈或运行时堆栈的堆栈。关于激活记录或运行时堆栈,我不知道。我的问题是:
一个函数调用另一个函数并且链继续。但是持续多久?这个嵌套调用可以持续多久?他们依赖什么?是机器的 OS 或位架构吗?
激活记录包含哪些内容?它的结构是什么样的?本地数据是否也传递给了这个?
为此类函数(具有多个嵌套调用或递归函数)的堆栈溢出设置了哪些参数?我的意思是如何提前知道以避免溢出。
答案 0 :(得分:2)
"激活记录"是一个抽象的概念,对于学术界对算法进行正式分析非常有用,而且对实际实现并不太关注。
真实系统具有函数参数,局部变量,保存的寄存器值,返回地址等。某些系统使用CPU寄存器来处理所有这些信息。
由于堆栈指针通常是保存的寄存器之一,因此在许多环境中,人们可以“走”"堆栈通过使用帧指针寄存器来定位堆栈指针和帧指针的保存值,因为它存在于调用者中,并根据需要重复。如果编译器设置如"帧指针省略"被激活(或者系统没有使用帧指针寄存器),这可以使用堆栈指针与调试元数据相结合来完成。
如果您正在编写这样的堆栈步行算法或计算总堆栈使用量,则需要关注堆栈布局。它还有助于理解堆栈变量中的缓冲区溢出如何导致覆盖返回地址"返回到libc"利用。
但是对于大多数用途,例如解释递归函数如何具有局部变量的多个实例,或者局部变量的别名(指针和引用)的生命周期,一堆激活记录的概念模型就足够了。
答案 1 :(得分:1)
如果您的函数在堆栈上创建局部变量,您将看到该变量的地址随着堆栈变深而变化。您可以通过跟踪该数字的变化来大致了解您已经消耗了多少堆栈。这样做是为了你的教育 - 而不是实际的代码。像这样:
#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个字节。
答案 2 :(得分:0)
&#34;激活记录&#34;是&#34;堆栈框架的代名词&#34; - 我不得不这样看,因为我对这个词并不熟悉。
function beginFoo() {
foo(0);
}
function foo(int depth) {
if( depth > 10 ) return; // avert possible stack-overflow
foo(depth + 1);
}