检测堆栈已满

Posted

技术标签:

【中文标题】检测堆栈已满【英文标题】:Detecting that the stack is full 【发布时间】:2012-06-20 05:53:32 【问题描述】:

在编写 C++ 代码时,我了解到使用堆栈来存储内存是个好主意。

但最近我遇到了一个问题:

我有一个实验,其代码如下所示:

void fun(const unsigned int N) 
    float data_1[N*N];
    float data_2[N*N];

    /* Do magic */

代码随机出现序列错误,我不知道为什么。

原来问题是我试图在我的堆栈上存储很大的东西,有没有办法检测到这个?或者至少检测到它出错了?

【问题讨论】:

您的问题是关于 C 还是 C++? 我认为你不能使用 C/C++ 中的变量来初始化堆栈上的数组! 您的数据有多大?我建议您使用 malloc/free 在堆中存储 100KB 以上的数据。 (其他人可能会建议一个更低的界限) 哦,拜托,你怎么能抗拒使用***标签? g++ 也允许在 C++ 中使用 VLA,作为扩展。 【参考方案1】:
float data_1[N*N];
float data_2[N*N];

这些是可变长度数组 (VLA),因为 N 不是常量表达式。参数中的 const-ness 仅确保 N 是只读的。它不会告诉编译器N 是常量表达式。

仅在 C99 中允许使用 VLA;在其他版本的 C 和所有版本的 C++ 中,它们是不允许的。但是,一些编译器提供 VLA 作为编译器扩展功能。如果您使用 GCC 进行编译,请尝试使用 -pedantic 选项,它会告诉您这是不允许的。

现在为什么你的程序会出现段错误,可能是因为stack-overflow 的值很大,因为N * N 的值很大:


考虑将std::vector 用作:

#include <vector> 

void fun(const unsigned int N) 

  std::vector<float> data_1(N*N);
  std::vector<float> data_2(N*N);

  //your code

【讨论】:

我在 questing 中添加了 const :-) const 函数参数在这里没有任何改变,该值仍然只在运行时才知道,所以你仍然依赖于编译器扩展。无论如何,无论是 C 还是 C++,关键是 - 如果您需要大量内存,请改用堆(通过 std::vector)。 @MartinKristiansen:我编辑了我的答案,说 “参数中的 const-ness 仅确保 N 是只读的。它不会告诉编译器 N是常量表达式。” @MartinKristiansen:如果您使用 GCC 编译代码,请尝试使用 -pedantic 选项。 @SPIRiT_1984:事实上,从一开始就使用malloc 是个坏主意。需要动态数组时使用std::vector。当您非常有理由这样做时,请使用malloc/new 并自己管理内存。【参考方案2】:

很难检测到堆栈已满,而且根本不便携。最大的问题之一是堆栈帧的大小是可变的(尤其是在使用可变长度数组时,这实际上只是一种更标准的方式来做人们以前使用alloca() 所做的事情)所以你不能使用简单的代理比如堆栈帧的数量。

大部分可移植的最简单方法之一是将变量(可能是char 类型,以便指向它的指针是char*)放在堆栈的已知深度然后通过简单的指针算法测量从该点到当前堆栈帧中的变量(相同类型)的距离。加上您将要分配多少空间的估计,您可以很好地猜测堆栈是否即将炸毁您。这样做的问题是你不知道堆栈的增长方向(不,它们并不都在同一个方向增长!)并且计算堆栈空间的大小本身就相当混乱(你可以尝试系统限制之类的东西,但它们真的很尴尬)。加上黑客系数非常高。

我看到的另一个技巧仅在 32 位 Windows 上使用是尝试alloca() 足够的空间并处理如果空间不足会发生的系统异常。

int have_enough_stack_space(void) 
    int enough_space = 0;

    __try            /* Yes, that's got a double-underscore. */
        alloca(SOME_VALUE_THAT_MEANS_ENOUGH_SPACE);
        enough_space = 1;
      __except (EXCEPTION_EXECUTE_HANDLER) 

    return enough_space;

此代码非常不可移植(例如,不要指望它可以在 64 位 Windows 上运行),并且使用较旧的 gcc 构建需要一些讨厌的内联汇编程序!结构化异常处理(这是一种用途)是 Windows 上最黑暗的黑魔法之一。 (并且不要从 __try 构造内部 return。)

【讨论】:

这一切都基于 Tcl 8.4 实现中的堆栈检查代码;使用 SEH 的代码在 8.5 中转换为使用第一个基于指针的方法,并在 8.6 中完全删除(它使用“无堆栈”引擎实现,因此几乎不需要这种东西)。 顺便说一句,将可变大小的数组存储在堆上会更加可靠,尤其是如果您可以使用 RAII 技术来确保可靠删除。【参考方案3】:

尝试使用 malloc 等函数。如果找不到您请求的大小的内存块,它将显式返回 null。

当然,在这种情况下,别忘了在函数结束时释放这块内存,在你完成之后。

此外,您可以检查编译器的设置,以及它生成二进制文件的堆栈内存限制。

【讨论】:

Malloc 确实有帮助 - 使用 malloc 是我的解决方案,但这意味着现在我有一个要跟踪的指针。 是的,指针确实如此,但由于它驻留在单个函数中,因此没有问题。只需在函数末尾添加delete @MartinKristiansen 您无需在每次使用动态内存时都跟踪指针。有一个称为RAII 的范例可以简化这一点,谷歌就可以了。同时,使用本地 std::vector 对象 如果你想动态分配数组,使用std::vector是我的首选。 @JoachimPileborg:由于存在异常,在 C++ 中手动跟踪分配的内存困难的。就像 Kos 所说的,最好的解决方案是通过使用向量来依赖 RAII。【参考方案4】:

人们说使用堆栈而不是堆内存更好的原因之一可能是因为当您离开函数体时,分配在堆栈顶部的变量将自动弹出。为了存储大块信息,通常使用堆内存和其他数据结构,如链表或树。此外,在堆栈上分配的内存是有限的,而且比在堆空间中分配的内存要少得多。我认为更好地管理内存分配和释放,而不是尝试使用堆栈来存储大数据。

您可以使用框架来管理您的内存分配。您也可以使用 VDL 来检查您的内存泄漏和未释放的内存。

【讨论】:

这就是我的意思,我编写的代码“主要”用于小“N”,但偶尔会出现大 N。【参考方案5】:

有没有办法检测到这个?

没有,一般来说。

堆栈大小取决于平台。通常,操作系统决定堆栈的大小。因此,您可以检查您的操作系统(Linux 上的ulimit -s)以查看它为您的程序分配了多少堆栈内存。

如果您的编译器支持stackavail(),那么您可以检查它。在不确定是否会超出堆栈限制的情况下,最好使用堆分配内存。

【讨论】:

但事情是这样的,我正在使用一些高端硬件的 Linux 机器上进行开发,并且花了很多时间弄清楚为什么我的代码不能在我的 MacBookPro 上运行。 Linux 机器使用 4 MB 堆栈分配没有问题,但 OSX 机器不喜欢它。

以上是关于检测堆栈已满的主要内容,如果未能解决你的问题,请参考以下文章

堆栈模拟队列

7-22 堆栈模拟队列

PTA-7-22 堆栈模拟队列

堆栈 Cookie 检测代码检测到基于堆栈的缓冲区溢出

为啥粉碎后没有立即出现“检测到堆栈粉碎”?

检测内核的堆栈溢出