堆栈从局部变量溢出?

时间:2014-04-17 18:27:32

标签: c++ c compiler-construction stack-overflow c-preprocessor

首先让我说我的问题不是关于堆栈溢出,而是关于实现它的方法,没有编译时错误\警告。 我知道(第一手)你可以通过递归溢出堆栈:

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字节也会导致我的机器上的堆栈溢出

所以,我想知道:

  1. 由于我没有尝试过,如果我宣布足够的变量,那么 堆栈溢出?
  2. 有没有办法使用预处理器或其他编译时工具&#34; (比如C++ template meta-programming)做第一件事,即让它声明很多局部变量,以某种方式导致它循环?如果是这样,怎么样?
  3. 这是理论上的 - 有没有办法知道程序的堆栈是否会在编译时溢出?如果是的话,请解释。

5 个答案:

答案 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。因此,在编译时,代码分析报告主处理器代码中的最大深度以及处理中断的最大深度。但分析并未证明这两者的最大组合深度会发生。

根据处理器类型,可以在编译时分配足够的堆栈,防止可能的溢出。