在测试我的堆栈实现(使用链表)时,我发现了一件有趣的事情。有一个测试代码可以重现它:
#include <iostream>
using namespace std;
struct Node {
int val;
Node *prev;
};
int main() {
Node *first = new Node;
first->val = 20;
first->prev = NULL;
cout << "first:" << first << endl;
Node *p = new Node;
p->val = 40;
p->prev = first;
delete p;
cout << "p->val:" << p->val << endl;
cout << "p->prev:" << p->prev << endl;
}
输出:
first:0x22cfc20
p->val:0
p->prev:0x22cfc20
但是,如果我按照struct Node
的定义交换元素的顺序:
struct Node {
Node *prev;
int val;
};
输出
first:0x195dc20
p->val:40
p->prev:0
当然,在这两种情况下它都是未定义的行为,但也许它存在一些合理的解释,为什么它以这种方式工作?或者它只是随机的?我尝试多次运行代码但每次都给出相同的输出(特定的地址值除外)并且从不压碎。
答案 0 :(得分:2)
未定义的行为根本没有指定的所有特定行为。可以允许实施程序使您的程序正常工作,或关闭计算机,格式化硬盘,在猫咪内部打开黑洞,甚至在黑洞内打开猫。
如果程序试图对内存执行无效操作,大多数实现将暂停执行。有时候,如果你运气不好,你的程序会很好用,就像你的例子一样。
有了这样的说法:有数百个理由可以访问已删除的内存,但是对此行为的任何假设都是无效的,并且可能因任何原因而产生不同的结果。
答案 1 :(得分:2)
正如其他人所说的那样,我们很少能够明确地说出这种情况,不仅因为未定义的行为,还因为我们对编译的平台一无所知。
但有一些事情可以说:
更改结构中成员的顺序可能会导致编译器在成员之间或结构末尾应用不同的填充,以便满足其当前正在编译的任何对齐规则。
如果在这种情况下确实发生了这种情况,那么它可能会影响堆中分配的内存布局,从而影响在释放堆空间然后重用堆空间时会覆盖哪些数据。这可以解释为什么结果不同。