在析构函数调用未定义行为后访问内存?
Is accessing memory after a destructor call undefined behavior?
我想知道以下是否未定义?
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);
}
我想知道这通常是件好事,但我特别想知道的原因是我有一个由数组组成的数据结构,其中的桶可以通过设置一个值来为空,有点像哈希 table 数组中的桶。当桶清空时调用析构函数,但在调用析构函数后检查并设置空状态我想知道它是否非法。
详细一点,假设我有一个对象数组,每个对象都可以表示每个桶中的 null,例如:
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.
}
是的,主要是。 Handle::value
只是指向类型 Handle
的指针的偏移量,因此无论您将其指向何处,它都会起作用,即使当前未构造包含对象。如果你要使用任何带有 virtual
关键字的东西,这最终会被破坏。
p[50].~Handle();
然而,这是一个不同的野兽。永远不要手动调用析构函数,除非您还显式调用了带有 new 放置的构造函数。仍然不违法,但很危险。
delete[] p;
(在你的例子中省略了!)是你最终进行双重破坏的地方,此时你已经远远超出了 UB,直接进入“它已损坏”域。
是的,它将是 UB:
Once a destructor is invoked for an object, the object's lifetime ends; the behavior is undefined if the destructor is invoked for an object whose lifetime has ended ([basic.life]).
[Example 2: If the destructor for an object with automatic storage duration is explicitly invoked, and the block is subsequently left in a manner that would ordinarily invoke implicit destruction of the object, the behavior is undefined. — end example]
p[50].~Handle();
和后来的 delete[] p;
将使其调用生命周期结束的对象的析构函数。
对于对象生命周期结束后的 p[50].value = 0;
,这适用:
Before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that represents the address of the storage location where the object will be or was located may be used but only in limited ways. For an object under construction or destruction, see [class.cdtor]
. Otherwise, such a pointer refers to allocated storage ([basic.stc.dynamic.allocation]
), and using the pointer as if the pointer were of type void*
is well-defined. Indirection through such a pointer is permitted but the resulting lvalue may only be used in limited ways, as described below. The program has undefined behavior if:
6.2 - the pointer is used to access a non-static data member or call a non-static member function of the object
我想知道以下是否未定义?
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);
}
我想知道这通常是件好事,但我特别想知道的原因是我有一个由数组组成的数据结构,其中的桶可以通过设置一个值来为空,有点像哈希 table 数组中的桶。当桶清空时调用析构函数,但在调用析构函数后检查并设置空状态我想知道它是否非法。
详细一点,假设我有一个对象数组,每个对象都可以表示每个桶中的 null,例如:
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.
}
是的,主要是。 Handle::value
只是指向类型 Handle
的指针的偏移量,因此无论您将其指向何处,它都会起作用,即使当前未构造包含对象。如果你要使用任何带有 virtual
关键字的东西,这最终会被破坏。
p[50].~Handle();
然而,这是一个不同的野兽。永远不要手动调用析构函数,除非您还显式调用了带有 new 放置的构造函数。仍然不违法,但很危险。
delete[] p;
(在你的例子中省略了!)是你最终进行双重破坏的地方,此时你已经远远超出了 UB,直接进入“它已损坏”域。
是的,它将是 UB:
Once a destructor is invoked for an object, the object's lifetime ends; the behavior is undefined if the destructor is invoked for an object whose lifetime has ended ([basic.life]).
[Example 2: If the destructor for an object with automatic storage duration is explicitly invoked, and the block is subsequently left in a manner that would ordinarily invoke implicit destruction of the object, the behavior is undefined. — end example]
p[50].~Handle();
和后来的 delete[] p;
将使其调用生命周期结束的对象的析构函数。
对于对象生命周期结束后的 p[50].value = 0;
,这适用:
Before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that represents the address of the storage location where the object will be or was located may be used but only in limited ways. For an object under construction or destruction, see
[class.cdtor]
. Otherwise, such a pointer refers to allocated storage ([basic.stc.dynamic.allocation]
), and using the pointer as if the pointer were of typevoid*
is well-defined. Indirection through such a pointer is permitted but the resulting lvalue may only be used in limited ways, as described below. The program has undefined behavior if:6.2 - the pointer is used to access a non-static data member or call a non-static member function of the object