在调用和从方法调用返回的情况下,C ++中的一个简单示例导致堆栈下溢?
我熟悉调用约定,即thiscall
,stdcall
和cdecl
以及它们清理堆栈的方式。编译器生成的代码不会自动处理堆栈下溢吗?
有什么情况可以让我遇到堆栈下溢问题?
答案 0 :(得分:11)
我能看到实际发生这种情况的唯一方法是,如果你声明一个函数使用stdcall
(或指定被调用者清理堆栈的任何其他调用约定),然后通过函数指针调用该函数指定为cdecl
(或调用者清理堆栈的任何其他调用约定)。如果你这样做,被调用的函数会在返回之前弹出堆栈,然后调用者也会弹出堆栈导致下溢和可怕的事情。
在成员函数的特定情况下,调用约定通常称为thiscall
,调用者或被调用者是否清除堆栈取决于编译器。
有关调用约定的详细信息,请参阅here。
答案 1 :(得分:5)
我不确定您是否在讨论数据结构堆栈及其中的下溢问题。至于stack(data structure) underflow
问题,这里有一个解释。
stack
是一种后进先出(LIFO)抽象数据类型和数据结构。堆栈可以将任何抽象数据类型作为元素,但只有三个基本操作: 推送 , pop 和 堆叠顶部 。
push 操作会将新项目添加到堆栈顶部,或者如果堆栈为空则初始化堆栈。如果堆栈已满并且没有足够的空间来接受给定项目,则认为堆栈处于溢出状态。 pop操作从堆栈顶部删除一个项目。
pop 要么显示以前隐藏的项目,要么导致空堆栈,但如果堆栈为空则会进入 下溢状态 (这意味着要删除的堆栈中不存在任何项目)。
堆栈顶部 操作从最顶层位置获取数据并将其返回给用户而不删除它。如果堆栈为空,堆栈顶部操作也会出现相同的下溢状态。
考虑一个堆栈实现示例:
template <class Item> class Stack
{
public:
bool isEmpty() const;
size_t size() const;
Item pop();
void push(const Item& it);
private:
};
现在考虑在此堆栈上执行以下操作。
C++ command resulting stack
------------------------------------------------
Stack<int> S;
_____ (empty stack of ints)
S.push(7);
| 7 | <-- top
-----
S.push(2);
| 2 | <-- top
| 7 |
-----
S.push(73);
|73 | <-- top
| 2 |
| 7 |
-----
S.pop();
| 2 | <-- top
| 7 | -----
S.pop();
-----
S.pop();
| 7 | <-- top
-----
S.pop();
----- (empty)
S.pop();
ERROR "stack underflow"
答案 2 :(得分:1)
通常情况下,编译器会处理这个问题。实际上,我能想到的唯一可能导致堆栈下溢的方法是调用一个调用约定实现的方法,就像它使用另一个调用约定一样。
答案 3 :(得分:1)
如果您处于可能发生调用堆栈下溢的位置,那么您的程序可能会在发生之前死于暴力死亡。至少,如果我理解函数调用工作的方式是准确的。
基本上,它发生的唯一方法是调用一个函数,其中被调用者清理堆栈,并且它弹出太多的值...如果调用者认为函数接受两个参数,但实际上是被调用者需要三个,这可能会发生。变量将是一个函数,其中被调用者清理堆栈,然后调用者再次清理堆栈,如果你的调用约定错误可能会发生。在这两种情况下,当你去链接并且错位的名字错误时你可能会遇到问题,但也许你真的很不走运。
无论哪种方式,重要的是在函数调用之后,堆栈比它应该的短一个或多个字节。从理论上讲,程序将继续运行,关闭正确数量的数据,但剩余的一个或多个字节短于预期。最终,没有更多数据可以弹出,并且您有一个堆栈下溢。
但是,在引用堆栈时,地址相对于顶部。因此,如果堆栈顶部有三个字节,编译器将在[堆栈顶部+3]查找特定对象。如果堆栈的结束时间短于预期,并且在错误的位置查找该对象,它仍然会这样做。假设对象甚至还在那里......它可能已经意外地被弹出了。当你到达任何函数的末尾时,由于同样的原因,它可能无法找到正确的返回地址,但即使它确实如此,所有对象突然被破坏也是一个非常可怕的情况。
警告:这完全基于这样的假设,即现代系统的行为与我十年前使用的旧微控制器的行为相同。也许他们比现在更聪明。