【问题标题】:Deleting a heap then dereferencing a pointer to that memory删除堆然后取消引用指向该内存的指针
【发布时间】:2010-10-30 00:02:07
【问题描述】:

这是来自练习的代码:

#include <iostream>
using namespace std;

int main() {
    int n = 13;
    int* ip = new int(n + 3);
    int* ip2 = ip;
    cout << *ip << endl;
    delete ip;
    cout << *ip2 << endl;
    cout << ip << tab << ip2 << endl;
}

当分配给堆上的 int 的空间被删除时,我认为取消引用指针会产生某种内存错误。相反,它返回 0。

这是为什么?

【问题讨论】:

  • 感谢回复:未定义的行为。假设内存没有被另一个程序覆盖,删除函数似乎在堆的第一个字中写入了一个空值。你觉得这很奇怪吗?
  • 好的。我想我找到了归零之谜的答案:Stroustrup 说“C++ 明确允许删除的实现将左值操作数归零,我曾希望实现能够做到这一点,但这个想法似乎并没有变得流行实施者。”我尝试在“删除 ip”之后执行“删除 ip2”,这给出了双重免费错误。我想我可以检查一下程序集,看看到底发生了什么。

标签: c++ pointers heap-memory


【解决方案1】:

取消引用无效指针会导致每个规范未定义的结果。不保证会失败。

通常(取决于 CPU/OS/编译器/...),编译器根本不关心它。它只是给出了当前在该内存地址的内容。例如,在 x86 架构中,只有当地址位于未映射到您的进程的内存页面中(或者您的进程无权访问该地址)时,您才会看到错误,因此 CPU 会抛出异常(保护错误)操作系统将适当处理(并且可能使您的进程失败)。有时使用一个技巧来使访问地址0 总是导致访问冲突:操作系统将页表中地址空间的第一页的读/写位设置为 0,以便对该页的任何访问将始终产生异常。

【讨论】:

  • 谢谢。但我认为我错过了一些东西,因为没有生成页面错误,但是取消引用产生零。现在,如果它仍然指向同一个内存区域,那么您会期望旧值在那里(假设没有被覆盖)。我已经多次运行该程序,并且指针不为空,所以还有其他事情发生。这是 IA-32 架构上的 gcc 编译器。
  • bugmenot77:这只是一个旁注。我的意思是说大多数 C/C++ 实现并不真正关心,您有时看到的错误来自 OS/CPU,而不是编译器。关于您的问题,当您删除指针时,指针本身不会自动设置为零。该内存位置的值可能会被覆盖(您不能假设任何事情)。无论如何,对此没有任何保证。你根本不应该依赖一个无效的指针。
  • 此外,调试版本通常会故意用一些值填充内存以帮助调试。
  • 谢谢。让我困惑的是零填充。我运行了几次迭代以查看它是否曾经非零以消除覆盖情况(没想到会覆盖,因为这是一台单用户台式机,没有其他任何事情发生)。你说的回复:调试构建是有道理的。这不是调试,这就是为什么它让我感到惊讶,好像删除是用零覆盖的,它会暗示性能影响。无论如何,这似乎是一个谜,也许只有懂 gcc、linux 和 x86 的人才知道。
【解决方案2】:

这种行为是未定义的,所以会发生什么取决于实现和系统。

【讨论】:

    【解决方案3】:

    取消引用ip 指针将返回当时在该内存位置发生的情况。

    它返回 0,因为这是 ip 的四个字节在转换为整数时碰巧编码的内容。

    在指针被删除后解除引用是不可预测的。它可能为零,如果该内存已在其他地方重新分配,它可能是别的东西。

    你很幸运,当你运行你的程序时它是 0。

    【讨论】:

    • “取消对 ip 指针的引用将返回当时在该内存位置发生的情况。” 你不知道。结果是未定义。 C++ 是一种抽象,而不是物理机器指令的一对一映射。
    • 我在下一段中确实写过
    【解决方案4】:

    对 Mehrdads 的进一步回答,在带有 glibc 的 gcc 中,表示内存堆的数据结构存储在返回的内存块中,以节省内存(即,它是一个侵入式列表)。因此,当一块内存被释放时,它会被添加到空闲列表中。我的猜测是,在 free 之后写入的 0 表示这是空闲块列表的最后一个元素(释放内存的第一个指针大小的字将包含列表 next 指针)。

    如果您要在再次取消引用此块之前分配并释放更多内存,则在将新项目添加到空闲列表的末尾时,该值会发生变化。这是库实现决策影响“未定义”行为期间发生的事情的一种方式。在这种情况下,glibc 开发人员利用了这种行为未定义的事实来提高他们的内存分配器的空间效率。

    如果您在 valgrind 下运行程序,它会为您捕获这些错误。在任何情况下,始终远离未定义的行为,因为它很可能在不同的平台上有所不同,甚至在同一平台上的不同构建(例如调试与发布)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-27
      • 1970-01-01
      • 2020-03-09
      • 2013-01-04
      • 2015-02-07
      • 1970-01-01
      • 2020-03-30
      • 1970-01-01
      相关资源
      最近更新 更多