首先让我说我的问题不是关于堆栈溢出,而是关于实现它的方法,没有编译时错误\警告。 我知道(第一手)你可以通过递归溢出堆栈:
void endlessRecursion()
{
int x = 1;
if(x) endlessRecursion(); //the 'if' is just to hush the compiler
}
我的问题是,是否可以通过声明太多局部变量来溢出堆栈。 显而易见的方法就是声明一个巨大的数组:
void myStackOverflow()
{
char maxedArrSize[0x3FFFFFFF]; // < 1GB, compiler didn't yell
}
实际上,即使0xFFFFF字节也会导致我的机器上的堆栈溢出
所以,我想知道:
答案 0 :(得分:3)
是的,分配大量内存会导致堆栈溢出。是否分配一个大变量或许多小变量并不重要;总大小是相关的。
您无法使用预处理器执行编译时循环,但您可以实现一些快捷方式,这些快捷方式可让您生成大量代码而无需全部输入。例如:
#define DECLARE1 { int i;
#define END1 }
#define DECLARE2 DECLARE1 DECLARE1
#define END2 END1 END1
#define DECLARE4 DECLARE2 DECLARE2
#define END4 END2 END2
等等。这将多个int i;
声明放在嵌套块中,确保所有对象同时存在,同时避免名称冲突。 (我无法想出一种方法来为所有变量赋予不同的名称。)
DECLARE4 END4
扩展为:
{ int i; { int i; { int i; { int i; } } } }
如果您的编译器在预处理后对行的长度施加限制,那么这不会起作用。
这里的教训是预处理器并不是真的为这种东西设计的。使用您喜欢的生成声明的脚本语言编写程序会更容易,也更灵活。例如,在bash中:
for i in {1..100} ; do
echo " int i$i;"
done
答案 1 :(得分:1)
问题3,我认为答案是否定的。编译器可以知道每个函数使用多少堆栈空间。但总堆栈空间取决于您的调用序列,这取决于在运行时评估的逻辑。只要没有递归调用,似乎可以确定所使用的堆栈空间的上限。如果该上限小于可用堆栈空间,则可以确定堆栈不会溢出。下限似乎也是可能的。如果它高于堆栈大小,则可以确定堆栈将溢出。
除了非常简单的程序,这只会给你边界,而不是精确的堆栈空间。一旦涉及到递归,我就不会认为在一般情况下你可以静态地确定上限。这几乎开始听起来像停止问题。
上面所有非常有限的选项显然都假设你有一个给定的堆栈大小。正如其他海报所提到的,编译器通常不知道堆栈大小,因为它通常是系统配置的一部分。
我见过的最接近的是静态分析仪。我似乎记得其中一些标志着大堆栈变量。但我怀疑他们试图分析实际的堆栈使用情况。它可能只是一个简单的启发式方法,它基本上告诉你堆栈上有大变量是一个坏主意,你可能想避免它。
答案 2 :(得分:0)
是的,太多的变量会炸掉堆栈。
答案 3 :(得分:0)
由于我没有尝试过,如果我声明了足够多的变量,那么堆栈是否会溢出?
是的,声明一个大尺寸的单个数组并在同一范围内声明多个变量是相似的。
有没有办法使用预处理器来做第一件事,即让它声明很多局部变量,通过某种方式导致它循环?
我不这么认为,因为你的编译(和内存分配)从main()
开始。无论您使用预处理器命令声明什么,都会在预处理阶段进行扩展。这个阶段不涉及任何内存分配。
这是理论上的 - 有没有办法知道程序的堆栈是否会溢出?
是的,对于linux系统,您可以获得分配给程序的堆栈内存量,除此之外的任何内容都会导致堆栈溢出。您可以阅读this link以获取有关如何了解任何流程的堆栈大小的详细信息。
答案 4 :(得分:0)
关于#3
有没有办法知道程序的堆栈是否会在编译时溢出?
是。这是嵌入式处理器的一些PIC编译器的标准功能,特别是那些使用Harvard architecture的处理器。但它需要付出代价:没有递归也没有VLA。因此,在编译时,代码分析报告主处理器代码中的最大深度以及处理中断的最大深度。但分析并未证明这两者的最大组合深度会发生。
根据处理器类型,可以在编译时分配足够的堆栈,防止可能的溢出。