使用示例解释编程中的堆栈溢出和堆溢出是什么?

18

可能是重复问题:
什么是堆栈溢出错误?

有人能告诉我堆栈溢出和堆溢出在程序中实际上是如何发生的,以及如何在编程中克服堆栈溢出-如何避免它?

3个回答

40

堆栈溢出

void stack_overflow(const char *x)
{
    char y[3];
    strcpy(y, x);
}

堆溢出

void heap_overflow(const char *x)
{
    char *y = malloc(strlen(x));
    strcpy(y, x);
}

分析

两个函数都超过了已分配的空间。

如果你调用 stack_overflow("abc"), 它会将4个字符 (包括 null 字符) 复制到为3个字符分配的空间中。之后会根据破坏产生的位置有所不同而导致不同的结果。变量 y 在堆栈上,因此它是堆栈溢出。

无论如何调用 heap_overflow(),它都会从堆上请求一个字节太少并写入超出结尾的内容。这种情况的难以察觉之处在于,大多数时候它似乎能够正常工作,因为堆系统会分配比您请求的空间更多的空间。然而,您可能会踩到控制数据,那么结果将难以预料。

堆溢出非常小,很难检测。堆栈溢出可能很小(如果传递的字符串足够短则不存在),也可能非常明显。当您写入超出已分配空间的内容时,通常会产生更加明显的影响,但是任何超出已分配空间的写入都会导致未定义的行为——任何事情都可能发生。

您可以通过了解要复制的对象有多大以及接收它的空间有多少,确保没有问题,并确保您不会复制超过空间的材料。每次都一定要这样做。


3
“堆溢出”和“栈溢出”的含义有两种观点。其中一种是缓冲区溢出,即在缓冲区(内存块)的边界之外写入(或读取)数据。本答案中所示为此观点。另一种观点是资源耗尽,即尝试使用比可用空间更多的空间。特别是栈是有限的资源(通常只有8 MiB,甚至在某些系统上更少),因此在栈上创建大型基于栈的变量(例如数组)可能会导致问题。使用虚拟内存后,很难用完堆,但泄露内存也会导致堆用尽。 - Jonathan Leffler
如果我们执行malloc(strlen(x)),堆溢出会如何发生?难道这不应该精确地分配与用户输入相等的空间吗?我不明白。 - dlock
@deadlock:strlen(x)不包括字符串末尾的空字节。如果您使用strcpy()x复制到分配的内存中,则会多复制一个字节,可能会造成灾难性后果。 - Jonathan Leffler
@JonathanLeffler 所以我们可以称之为“差一漏洞”。 - dlock
“它似乎能够工作,因为堆系统分配的空间比您请求的要多”这句话非常贴切。我也曾经遇到过这种情况。 - rustyMagnet
显示剩余5条评论

5
"

堆栈溢出"与"基于堆栈的缓冲区溢出"不同。前者是由于太深的激活记录造成的,例如无休止的递归调用。后者是由于不充分的边界检查引起的软件漏洞,这是最常被利用的漏洞。

"

-5

堆栈溢出:

 static void f(void) { f() ; }
 int main() { f() ; }

堆溢出:

 #include <stdlib.h>
 int main() { while (1) malloc (1000) ; }

编辑 显然这不是堆栈溢出的意思。请参见下面的评论。


4
这些显示堆栈耗尽和堆内存耗尽,但不显示堆栈溢出或堆内存溢出。 - Jonathan Leffler
2
错误答案为-1,malloc的头文件是stdlib.hmemory.h是另一个头文件。 - R.. GitHub STOP HELPING ICE
@Jonathan Leffler:我认为关于堆栈溢出的问题我是正确的。值得一提的是,维基百科(http://en.wikipedia.org/wiki/Stack_overflow)支持我的观点(并且该文章将您的想法描述为“堆栈缓冲区溢出”)。但是您对于堆溢出是正确的。 - TonyK
如果您区分'Stack Overflow'和'stack buffer overflow',那么您的'stack overflow'代码最终会耗尽堆栈(也称为'exhaustion'),但这也可以被视为溢出。很多时候,'Stack Overflow'被用作'stack buffer overflow'的简称,而您的示例并没有说明这一点。因此,我可以将您的'stack overflow'称为一种'stack overflow'的变体示例而不会有问题。 - Jonathan Leffler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接