【问题标题】:Pointer pointing to deleted stack memory指向已删除堆栈内存的指针
【发布时间】:2015-07-28 03:31:48
【问题描述】:

我相信我刚刚经历的被称为“未定义的行为”,但我不太确定。基本上,我在包含类地址的外部范围中声明了一个实例。在内部层中,我在 stack 上实例化了一个对象,并将该实例的地址存储到持有者中。

内部作用域逃逸后,我检查是否仍可以访问已删除实例的方法和属性。令我惊讶的是,它没有任何问题。

有没有简单的方法来解决这个问题?有没有办法可以从列表中清除已删除的指针?

示例:

std::vector<int*> holder; 
{
    int inside = 12;

    holder.push_back(&inside);
}
cout << "deleted variable:" << holder[0] << endl;

【问题讨论】:

  • 我相信这是未定义的行为;因为运行时没有换出内存中的页面或在同一个地方分配一个新的堆栈帧,其中一个变量覆盖了inside
  • 为了澄清,在这个例子中你使用堆栈分配变量inside,但是你在你的问题中说你的对象是在堆上分配的。如果你堆分配一个对象,它会一直存在,直到你在它上面调用delete,因此没有未定义的行为。如果要模拟堆分配,请将示例中的 int inside = 12; 更改为 int* inside = new int; *inside = 12;(需要注意的是,稍后您需要将其更改为 delete
  • 是否有充分的理由将地址放入列表中?因为一个简单的解决方法是避免这样做。
  • 如果我想解决这个问题,我想我的代码过于复杂了。如果非指针要进出范围,我无论如何都不应该添加它们..
  • 这个例子过于简单了。它没有证明这个问题。另外,您从未提及:您是否从堆中释放了实例?

标签: c++ pointers memory


【解决方案1】:

有没有简单的方法来解决这个问题?

当然,有很多方法可以避免此类问题。

最简单的方法是根本不使用指针——而是按值传递对象。即在您的示例代码中,您可以使用std::vector&lt;int&gt; 而不是std::vector&lt;int *&gt;

如果您的对象由于某种原因不可复制,或者足够大以至于您认为复制它们的成本太高,您可以改为在堆上分配它们,并使用 shared_ptr 或自动管理它们的生命周期unique_ptr 或其他一些智能指针类。 (请注意,按值传递对象比您想象的更有效,即使对于较大的对象也是如此,因为它避免了处理堆,这可能很昂贵......并且现代 CPU 在处理连续内存时效率最高。最后,现代 C++ 有各种优化,允许编译器在许多情况下避免实际进行数据复制)

一般来说,保留指向堆栈对象的指针不是一个好主意,除非您 100% 确定指针的生命周期将是它指向的堆栈对象生命周期的子集。 (即便如此,这也可能是个坏主意,因为在您转到下一份工作之后接管代码的下一个程序员可能不会看到这种微妙的危险,因此在更改时可能会无意中引入悬空指针错误代码)

内部范围逃逸后,我检查了是否仍然可以 访问被移除实例的方法和属性。出乎我的意料 它没有任何问题。

如果对象所在的内存尚未被其他任何内容覆盖,则可能会发生这种情况——但如果/当您取消引用无效指针时,绝对不要依赖该行为(或任何其他特定行为),除非您喜欢在调试器上花费大量时间来追踪随机崩溃和/或其他奇怪的行为 :)

有没有办法从列表中清除已删除的指针?

原则上,您可以将代码添加到对象的析构函数中,这些析构函数将遍历列表并查找指向自身的指针并删除它们。在实践中,我认为这是一种糟糕的方法,因为它会消耗 CPU 周期来尝试从错误中恢复,而更好的设计一开始就不允许进行。

顺便说一句,这不是主题,但您可能会感兴趣,Rust 编程语言旨在检测和防止此类错误by catching it at compile-time。也许有一天 C++ 会得到类似的东西。

【讨论】:

    【解决方案2】:

    没有像 deleted 指针这样的东西。指针只是一个数字,代表进程虚拟地址空间中的某个地址。即使堆栈帧早已消失,持有它的内存仍然可用,因为它是在线程启动时分配的,所以从技术上讲,它仍然是一个 valid 指针,就术语而言是有效的,你可以取消引用它并得到一些东西。但由于它指向的对象已经消失,有效术语将是悬空指针。道德是,如果您在堆栈帧中有指向对象的指针,则无法确定它是否有效,甚至无法使用 IsBadReadPtr 之类的函数(仅以 Win32 API 为例)。防止这种情况的最好方法是避免返回和存储指向堆栈对象的指针。

    但是,如果您希望跟踪堆分配的内存并在不再使用后自动释放它,您可以使用智能指针(std::shared_ptrboost::shared_ptr 等)。

    【讨论】:

    • 我认为将其称为悬空指针更准确,对象的生命周期已过期,但他愚蠢地将其地址保存在std::vector中。
    • @Blastfurnace 同意。谢谢)
    • 如果一个对象不再存在于一个指针指向的位置,那么它就是一个无效指针(不是一个有效的指针)。取消引用这样的指针会导致未定义的行为。此外,指针不仅仅是数字。一般来说,可能没有虚拟地址空间或堆栈帧。
    • 你能举一个这种情况的例子吗?
    • @Ari0nhh:在某些系统上,大量分配由操作系统直接处理。释放这些分配将内存直接返回给操作系统。这些指针的后续使用将出现段错误(未定义行为的一种)。至于不是数字的指针,常见的例子是指向成员的指针。
    猜你喜欢
    • 2020-03-09
    • 1970-01-01
    • 1970-01-01
    • 2015-11-27
    • 1970-01-01
    • 2020-01-03
    • 2016-11-14
    • 2013-07-23
    • 2021-09-01
    相关资源
    最近更新 更多