栈溢出 (stack overflow) 是指程序使用的栈内存超过系统分配的内存限制的情况。历史上,栈用于存储局部变量、返回地址和函数调用时的临时数据。在早期的 C 实现中,栈相对较小,并且未能防止越界。
问题 出现于当一个函数或函数调用链使用了过多的局部变量,或在没有退出条件的情况下调用递归函数,这就导致程序写入栈分配内存之外的数据,进而发生错误、崩溃和漏洞。
解决方案 是设计内存占用少的函数,避免深度或无限递归,不在栈上分配大型对象。操作系统可以通过内存段保护 (guard pages) 来防止溢出,但开发者必须编写不会导致溢出的代码。
导致栈溢出的代码示例由于无限递归:
void foo() { int arr[1000]; // 大的局部数组仅加剧了问题 foo(); // 无退出条件的递归调用 } int main() { foo(); return 0; }
关键特性:
如果在函数内部声明一个非常大的局部数组 (例如 int arr[1000000]),会发生什么?
答案:大型局部数组可能会立即占用整个栈。根据操作系统和编译器的不同,这将导致在调用函数时崩溃,甚至程序崩溃。
代码示例:
void func() { int arr[1000000]; // 占用大量内存 arr[0] = 1; }
递归是否总是导致栈溢出?
答案:不是,递归是有用的,只要深度有限。只有当递归深度很大或没有限制时才会发生溢出。
可以在函数内部放置大型静态数组以节省内存吗?
答案:不行,函数内部的静态数组仍然占用内存,但是在静态数据段而不是栈上。这并不总是节省内存,特别是在需要临时局部内存时。
代码示例:
void func() { static int arr[1000000]; // 不在栈上,但静态区域占用一直存在 }
程序员实现了通过递归进行快速排序,以对大数组进行排序,没有限制调用深度,也没有使用退出条件。代码在处理真实数据时导致了栈溢出。
优点:
缺点:
另一位程序员使用迭代实现,使用自己控制的小堆栈,控制递归深度,通过 malloc 分配大型临时数组。
优点:
缺点: