在递归函数中声明一个静态变量。堆栈溢出

时间:2018-12-07 15:29:11

标签: c++ recursion static stack

这段代码只是关于概念,没有任何意义。

void recur(int num)
{   
    static float tmp = num * num;
    if (num == 0)
    {
        return;
    }
    else
    {
        recur(num - 1);
    }
}

int main()
{
    recur(1000000);
}

我认为静态变量仅在内存中使用一个位置,但是调用main中的recur函数会导致堆栈溢出故障,这确实有意义是否在堆栈中声明了变量tmp,但在堆栈中声明了静态变量不是堆栈,对吧?

tmp变量的行为是什么?

谢谢

5 个答案:

答案 0 :(得分:4)

  

我认为静态变量仅在内存中使用一个位置

您的想法正确。

  

tmp变量的行为是什么?

tmp变量只有一个对象。对recur的所有调用都使用相同的对象。它在第一次调用该函数时初始化,并在main返回后销毁。


每个函数调用都会压低堆栈-除非已通过扩展内联调用优化了该调用(例如在尾部调用优化的情况下),并且深度递归很容易使堆栈溢出。

请注意,您的函数还具有一个参数,该参数具有自动存储功能,可能会加速堆栈的消耗。

答案 1 :(得分:1)

静态变量不在堆栈中。您可以将其视为全局变量。因为每次调用都会将参数“ num”压入堆栈,所以会出现堆栈溢出。此外,返回地址位于堆栈上,因此即使是“空”功能也将导致溢出(来自https://en.wikipedia.org/wiki/Call_stack的图像)

enter image description here

答案 2 :(得分:1)

在C和C ++中,函数的静态变量和全局变量存储在内存的.data部分中,该部分与堆栈和堆分开。

WiKi中有一个不错的diagram

如先前的回答和评论所指出的,每次您呼叫recur都是寄信人地址的地方,并且可能(取决于平台,我假设您正在x86上进行测试)参数在堆栈上分配。

您可以使用较小的递归(例如10次迭代)并打印出地址,以查看各节之间的差异,如下所示:

num

示例输出如下:

  

p:0x1c12c20 x:0x6011b8 z:0x7ffc0b6dd914
  tmp:0x6011c8 num:0x7ffc0b6dd8fc
  tmp:0x6011c8 num:0x7ffc0b6dd8dc
  tmp:0x6011c8 num:0x7ffc0b6dd8bc
  tmp:0x6011c8 num:0x7ffc0b6dd89c
  tmp:0x6011c8 num:0x7ffc0b6dd87c
  tmp:0x6011c8 num:0x7ffc0b6dd85c
  tmp:0x6011c8 num:0x7ffc0b6dd83c
  tmp:0x6011c8 num:0x7ffc0b6dd81c
  tmp:0x6011c8 num:0x7ffc0b6dd7fc
  tmp:0x6011c8 num:0x7ffc0b6dd7dc
  tmp:0x6011c8 num:0x7ffc0b6dd7bc

答案 3 :(得分:0)

每次调用recur函数都将需要分配至少num个参数,这会占用自动存储空间。

答案 4 :(得分:0)

我已经看过主要编译器的汇编,通过最大程度的优化,您应该不会出现任何堆栈溢出:

  • GCC :简化该函数以返回0,无需递归。
  • Clang :作为GCC
  • MSVC :使用内部跳转(有点尾部调用)执行回溯调用,只有第一个非递归调用的参数被压入堆栈。

因此,我想如果您为此微不足道的情况打开优化功能,则不会有堆栈溢出。

但是我想你的情况更复杂。

如果您的函数在没有任何静态变量的情况下不会导致任何堆栈溢出,然后在添加此静态变量的情况下导致堆栈溢出,则原因可能是对静态变量初始化的代码生成的副作用。

静态变量初始化由一种“互斥体”保护,以确保仅静态变量在多线程程序中仅初始化一次。这些互斥锁导致对库函数的调用。在调用函数之前,编译器必须确保在调用过程中保留调用者保存的,未使用的参数寄存器和被调用者保存的寄存器(它将不使用)。为此,编译器可以将寄存器值压入堆栈。

因此,即使静态变量初始化在程序流中仅发生一次,其简单存在也可以极大地更改生成的代码。