在编写 C++ 代码时,我了解到使用堆栈存储内存是个好主意。
但是最近我遇到了一个问题:
我有一个实验,其代码如下所示:
void fun(const unsigned int N) {
float data_1[N*N];
float data_2[N*N];
/* Do magic */
}
该代码随机出现了一个分段错误,我不知道为什么。
原来问题是我试图在我的堆栈上存储很大的东西,有没有办法检测到这个?或者至少检测到它出错了?
在编写 C++ 代码时,我了解到使用堆栈存储内存是个好主意。
但是最近我遇到了一个问题:
我有一个实验,其代码如下所示:
void fun(const unsigned int N) {
float data_1[N*N];
float data_2[N*N];
/* Do magic */
}
该代码随机出现了一个分段错误,我不知道为什么。
原来问题是我试图在我的堆栈上存储很大的东西,有没有办法检测到这个?或者至少检测到它出错了?
float data_1[N*N];
float data_2[N*N];
这些是可变长度数组 (VLA),N
不是常量表达式。参数中的const-ness仅确保它N
是只读的。它不会告诉编译器N
是常量表达式。
仅在 C99 中允许使用 VLA;在其他版本的 C 和所有版本的 C++ 中,它们是不允许的。但是,一些编译器提供 VLA 作为编译器扩展功能。如果您使用 GCC 进行编译,请尝试使用-pedantic
选项,它会告诉您这是不允许的。
现在为什么您的程序会给出段错误,可能是由于值较大而导致堆栈溢出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
}
很难检测到堆栈已满,而且根本不便携。最大的问题之一是堆栈帧的大小是可变的(尤其是在使用可变长度数组时,这实际上只是一种更标准的方式来做人们之前所做的事情alloca()
)所以你不能使用像数字这样的简单代理堆栈帧。
大多数可移植的最简单方法之一是将变量(可能是类型char
,以便指向它的指针是 a char*
)放在堆栈上的已知深度,然后测量从该点到变量(相同的变量)的距离type) 通过简单的指针算法在当前堆栈帧中。加上您将要分配多少空间的估计,您可以很好地猜测堆栈是否即将炸毁您。这样做的问题是你不知道堆栈的增长方向(不,它们并不都在同一个方向增长!)并且计算堆栈空间的大小本身就相当混乱(你可以尝试系统限制之类的东西,但它们真的很尴尬)。加上黑客因素非常高。
我在 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 上最黑暗的黑魔法之一。(并且不要return
从__try
构造内部。)
尝试使用 malloc 等函数。如果找不到您请求的大小的内存块,它将显式返回 null。
当然,在这种情况下,不要忘记在函数结束后释放此内存。
此外,您可以检查编译器的设置,以及它生成二进制文件的堆栈内存限制。
人们说使用堆栈而不是堆内存更好的原因之一可能是因为当您离开函数体时,分配在堆栈顶部的变量将自动弹出。为了存储大块信息,通常使用堆内存和其他数据结构,如链表或树。此外,在堆栈上分配的内存是有限的,而且比在堆空间中分配的内存要少得多。我认为最好更仔细地管理内存分配和释放,而不是尝试使用堆栈来存储大数据。
您可以使用管理内存分配的框架。您也可以使用 VDL 来检查您的内存泄漏和未释放的内存。
有没有办法检测到这个?
不,一般来说。
堆栈大小取决于平台。通常,操作系统决定堆栈的大小。因此,您可以检查您的操作系统(ulimit -s
在 linux 上)以查看它为您的程序分配了多少堆栈内存。
如果您的编译器支持stackavail()
,那么您可以检查它。在不确定是否超出堆栈限制的情况下,最好使用堆分配内存。