【问题标题】:Is accessing memory after a destructor call undefined behavior?在析构函数调用未定义行为后访问内存吗?
【发布时间】:2021-08-11 05:33:54
【问题描述】:

我想知道以下是否未定义?

int main()
{
    struct Doggy { int a; ~Doggy() {} };
    Doggy* p = new Doggy[100];

    p[50].~Doggy();

    p[50].a = 3; // Is this not allowed? The destructor was called on an
// object occupying that area of memory.
// Can I access it safely?
    if (p[50].a == 3);
}

我想这通常很好知道,但我特别想知道的原因是我有一个由数组组成的数据结构,其中桶可以通过设置一个值来为空,有点像桶一个哈希表数组。当桶被清空时,会调用析构函数,但是在调用析构函数后检查并设置空状态我想知道它是否非法。

稍微详细一点,假设我有一个对象数组,每个对象可以在每个桶中表示为空,例如:

struct Handle
{
    int value = 0; // Zero is null value
    ~Handle(){}
};

int main()
{
    Handle* p = new Handle[100];
    // Remove object 50
    p[50].~Handle();
    p[50].value = 0; // Set to null
    if (p[50].value == 0) ; // Then it's null, can I count on this?
   // Is this defined? I'm accessing memory that was occupied by
   // object that was destroyed.
}

【问题讨论】:

  • 您是否使用placement new 来创建存储桶?
  • 是的,如果在适当位置创建了一个新对象。但我想知道,因为对象本身可能是可空的,并且可以使用对象本身中设置为空值的成员来检查映射中的存储桶(值)中是否存在对象。但我想知道是否可以在调用析构函数后检查它是否为 null,就像我上面的例子一样。
  • 看来std::optional 在这里可能有用。
  • 是的,我可能会使用类似的东西,但我有兴趣从技术角度了解这个问题的答案。 optional 之类的东西会保留一个布尔值,对于一个 8 字节的结构来说,每个对象可能会浪费 7 个字节。这通常没什么大不了的,但是如果可以将空值存储在结构本身中,我有兴趣知道这个问题的答案。
  • stackoverflow.com/questions/6500313/… -- 顺便说一句。特别是使用向量 new 是一种代码味道。

标签: c++ language-lawyer undefined-behavior


【解决方案1】:

是的,它将是 UB:

[class.dtor/19]

一旦对象的析构函数被调用,对象的生命周期就结束了; 如果为生命周期已结束的对象调用析构函数,则行为未定义 ([basic.life])。

[示例 2:如果显式调用具有自动存储持续时间的对象的析构函数,并且随后以通常会调用隐式销毁对象的方式留下块,则行为未定义。 —结束示例]

p[50].~Handle(); 和更高版本的delete[] p; 将使它为一个生命周期已结束的对象调用析构函数。

对于对象生命周期结束后的p[50].value = 0;,适用:

[basic.life/6]

在对象的生命周期开始之前但在分配对象将占用的存储空间之后,或者,在对象的生命周期结束之后并且在重用或释放对象占用的存储空间之前 strong>,表示对象将或曾经位于的存储位置的地址的任何指针都可以使用,但只能以有限的方式使用。对于正在构建或销毁的对象,请参阅[class.cdtor]。否则,这样的指针指向已分配的存储空间 ([basic.stc.dynamic.allocation]),并且将指针用作 void* 类型的指针是明确定义的。允许通过这种指针进行间接访问,但生成的左值只能以有限的方式使用,如下所述。 如果

程序有未定义的行为

6.2 - 指针用于访问非静态数据成员或调用对象的非静态成员函数

【讨论】:

  • @Elliott 您不能将new[]free 混用,这是两个完全不同的API。
  • @Ext3h:谢谢,I was wrongconceptually malloc and new allocate from different heaps, so can’t free or delete each other’s memory. 稍后我会删除这条评论。
  • 这正是我要找的信息,显然是UB。
  • @Zebrafish 确实!很高兴为您提供帮助!
【解决方案2】:

是的,主要是。 Handle::value 只是一个指向Handle 类型指针的偏移量,所以它只会在你指向它的任何地方工作,即使当前没有构造包含对象。如果您要使用带有 virtual 关键字的任何内容,那么这最终会被破坏。

p[50].~Handle(); 然而,这是一个不同的野兽。除非您还使用placement new 显式调用了构造函数,否则您永远不应手动调用析构函数。仍然不违法,但很危险。

delete[] p;(在您的示例中省略!)是您最终遭受双重破坏的地方,此时您已经远远超出 UB,直接进入“它已损坏”域。

【讨论】:

  • 这里只是装模作样,只是因为一个程序肯定会导致seg。错误并不意味着它是未定义的。如果有内存泄漏,也是一样。
  • @Elliott 未定义行为是一个正式术语。你是对的,有明确定义的方法可以将 SIGSEGV 提升到进程中,但事实并非如此。
猜你喜欢
  • 2011-09-07
  • 2020-09-25
  • 2019-09-03
  • 2011-03-18
  • 2014-04-19
  • 2015-11-19
  • 1970-01-01
  • 1970-01-01
  • 2011-02-15
相关资源
最近更新 更多