3
#include <stdio.h>

int doHello(){
    doHello();
}

int main(){
    doHello();
    printf("\nLeaving Main");
    return 0;
}

当您运行此程序时,程序将退出,而不会在屏幕上打印消息“Leaving Main”。这是堆栈溢出的情况,由于哪个程序正在终止,但我在命令窗口上看不到任何错误消息。(在 Windows/Cygwin/ 上运行)

Q1。我没有在 doHello 函数中声明任何局部变量,但仍在使用堆栈。这是因为

  • 返回值
  • 存储有关函数调用的信息?

澄清

Q2。如何在你的程序中调试这种情况?我不是要调试我上面提到的无限循环。

例如:

#define SIZE 512*1024
void doOVerflow(){
   char str[SIZE];
   doHello();
}

void doHello(){
   char strHello[256];  // stack gets filled up at this point
   doNothing();         // program terminates and function doNothing does not get called
}

编辑:

Q3。什么信息存储在运行时堆栈中?

4

6 回答 6

10

通常是帧指针返回地址。参见例如维基百科的“调用堆栈”文章。如果你好奇:

$ gcc -S test.c  # <--- assembles, but does not compile, test.c; output in test.s
$ cat test.s
// [some contents snipped]
_doHello:
        pushl   %ebp        // <--- pushes address of stack frame onto stack
        movl    %esp, %ebp  // <--- sets up new stack frame
        call    _doHello    // <--- pushes return value onto stack, makes call
        popl    %ebp        // <--- pops address of stack frame off stack
        ret                 // <--- pops return value off stack, returns to it

为了好玩,试试“-fomit-frame-pointers”:

$ gcc -fomit-frame-pointers -S test.c
$ cat test.s
// [some contents snipped]
_doHello:
        call    _doHello   // <--- pushes return value onto stack, makes call
        ret                // <--- pops return value off stack, returns to it

为了更有趣,让我们看看当我们打开优化时会发生什么:

$ gcc -fomit-frame-pointers -O4 -S test.c # <--- heavy optimization
$ cat test.s
// [some contents snipped]
_doHello:
L2:
        jmp     L2         // <--- no more stack operations!

至少在我的设置(目前是cygwin)上,最后一个版本将永远运行,而不是退出。

要诊断此类问题,您可以在您喜欢的调试器(例如 Microsoft Visual C++ 或 gdb)中运行,或使用这些调试器检查通常由大多数系统生成的堆栈转储(.core 或 .stackdump 文件)。

如果您的调试器支持这一点,您也可以在堆栈顶部附近设置硬件断点——任何写入此变量的尝试,您的堆栈都可能已满。一些操作系统有额外的机制来提醒您堆栈溢出。

最后,调试环境(如valgrindApplication Verifier )可能会有所帮助。

于 2009-08-15T19:40:19.107 回答
6

每次调用函数时,调用的地址都会存储在堆栈中。

于 2009-08-15T19:21:47.460 回答
3

即使您没有任何局部变量,堆栈帧仍将包含返回地址。

注意:具有足够高优化级别的现代编译器应该能够执行尾调用优化,从而避免堆栈溢出。

于 2009-08-15T19:25:04.160 回答
2

难道这是因为一个。返回值 B. 存储有关函数调用的信息?

在您的情况下,使用堆栈的是返回地址。

如何在你的程序中调试这种情况?

不要一开始就写它们。递归函数必须始终具有终止条件。

Q3。运行时堆栈中存储了哪些信息?

对于大多数语言,每个函数调用的局部变量,以及每个函数调用的返回地址。

当进行函数调用时,当前执行点被压入堆栈并执行该函数的代码。当该执行结束时,堆栈上的值被弹出并且执行路径返回到该点。在您的代码中,返回地址被压入堆栈,函数被调用 - 函数调用的第一件事就是调用自身,这意味着返回地址被压入堆栈,函数被调用,所以开,无限期,或者直到你得到一个堆栈溢出。

于 2009-08-15T19:21:35.073 回答
1

其他人已经解决了您为什么会出现堆栈溢出的问题。

只是关于调试的额外说明。大多数现代调试器会在堆栈溢出时中断,并让您查看调试器中的调用堆栈。那时,您会一遍又一遍地看到相同的功能。如果您没有使用现代调试器来诊断此类问题,那么您就是在让自己变得不必要的困难。

于 2009-08-15T19:37:37.130 回答
1

拆解分析功能。

让我们来看看你的第一个代码(保存为so.c):

int doHello(){
    doHello();
}

int main(){
    doHello();
    printf("\nLeaving Main");
    return 0;
}

编译和反汇编 doHello():

$ gcc -Wall -ggdb3 -O0 so.c -o so
$ gdb --annotate=3 so
(gdb) disassemble doHello
Dump of assembler code for function doHello:
0x080483e4 <doHello+0>: push   %ebp
0x080483e5 <doHello+1>: mov    %esp,%ebp
0x080483e7 <doHello+3>: sub    $0x8,%esp
0x080483ea <doHello+6>: call   0x80483e4 <doHello>
0x080483ef <doHello+11>:        leave
0x080483f0 <doHello+12>:        ret
End of assembler dump.

现在,我们有了函数的汇编代码,我们可以准确地看到它在做什么。碰巧,这个问题的答案就在我们眼前:第一条指令将一个双字(返回指针)压入堆栈。

希望这可以澄清事情。

干杯。

于 2009-08-15T20:48:54.087 回答