C运行时stackoverflow

时间:2009-08-15 19:18:06

标签: c stack-overflow

#include <stdio.h>

int doHello(){
    doHello();
}

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

当您运行此程序时,程序退出而不在屏幕上打印“Leaving Main”消息。这是Stack Overflow的情况,因为程序正在终止,但我在命令窗口中看不到任何错误消息。 (跑上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。运行时堆栈中存储了哪些信息?

6 个答案:

答案 0 :(得分:10)

通常是帧指针返回地址。参见例如wikipedia's "call stack" article。如果你很好奇:

$ 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文件)生成的stackdumps那些调试器。

如果您的调试器支持此操作,您也可以在堆栈顶部附近设置硬件断点 - 任何尝试写入此变量,并且您的堆栈可能已满。某些操作系统具有其他机制来提醒您堆栈溢出。

最后,调试环境(例如 valgrind Application Verifier )可能有所帮助。

答案 1 :(得分:6)

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

答案 2 :(得分:3)

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

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

答案 3 :(得分:2)

  

这是因为。返回值   湾存储的信息   函数调用?

在您的情况下,它是消耗堆栈的返回地址。

  

如何调试此类案例   程序

首先不要写它们。递归函数必须始终 有终止条件。

  

Q3。运行时存储的信息   堆栈?

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

进行函数调用时,当前执行点为 将om按到堆栈并执行该函数的代码。当执行结束时,将弹出堆栈上的值,执行路径将返回到该点。在您的代码中,返回地址被压入堆栈,函数是 调用 - 函数调用的第一件事就是调用自身,这意味着返回地址被压入堆栈,函数被调用,依此类推,ad infimnitum,或者直到你得到堆栈溢出。

答案 4 :(得分:1)

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

关于调试的额外说明。大多数现代调试器都会在堆栈溢出时中断,让您查看调试器中的调用堆栈。那时,你会一遍又一遍地看到同样的功能。如果你没有使用现代调试器来诊断这类问题,你就会对自己造成不必要的困难。

答案 5 :(得分: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.

现在,我们有了函数的汇编代码,我们可以看到完全它正在做什么。碰巧的是,问题的答案就是盯着我们:第一条指令将dword(返回指针)推到堆栈上。

希望这能澄清事情。

干杯。