在不调用替换运算符 delete 的情况下取消分配由全局替换运算符 new 返回的指针是未定义的行为吗? (C++17)
Is it undefined behavior to deallocate a pointer returned by a global replacement operator new, without calling a replacement operator delete? (C++17)
在 SL operator new
未返回的非空指针上调用 SL operator delete
被视为未定义行为,如此处针对 (1) 和 (2) 所述:
https://en.cppreference.com/w/cpp/memory/new/operator_delete
The behavior of the standard library implementation of this function is undefined unless ptr is a null pointer or is a pointer previously obtained from the standard library implementation of operator new(size_t)
or operator new(size_t, std::nothrow_t)
.
因此,混合使用 operator new
、operator delete
和 operator new[]
、operator delete[]
也是未定义的行为。我在标准中找不到任何说明这是否也适用于调用用户分配方法的替换 operator new
和 operator delete
。例如:
void* operator new(std::size_t p_size)
{
void *ptr = UserImplementedAlloc(p_size);
return ptr;
}
void* operator new[](std::size_t p_size)
{
void *ptr = UserImplementedAlloc(p_size);
return ptr;
}
void operator delete(void* p_ptr)
{
UserImplementedFree(p_ptr);
}
void operator delete[](void* p_ptr)
{
UserImplementedFree(p_ptr);
}
以下是否未定义?假设 UserImplementedAlloc
总是 returns 一个正确的地址,而不是 nullptr
.
struct Simple
{
explicit Simple(); //Allocates m_bytes
~Simple(); //Frees m_bytes
char * m_bytes;
};
/*Placement new is not replaced or overridden for these examples.*/
//Example A
{
//Allocates and invokes constructor
Simple* a = new Simple();
//Invokes destructor
a->~Simple();
//Deallocates
UserImplementedFree(static_cast<void*>(a));
}
//Example B
{
//Allocates
void* addr = UserImplementedAlloc(sizeof(Simple));
//Invokes constructor
Simple* b = new (addr) Simple();
//Invokes destructor and deallocates
delete b;
}
我不是在寻找关于这是否是不良做法的讲座,我只是想确定这是否是定义的行为。
您的编译器版本的 delete 可能知道一些隐藏在实现中的关于依赖于类型的已删除指针的信息。
首先手动调用析构函数然后删除一个 void*(因为否则你会调用析构函数两次)是不安全的。您没有删除 C++ 语义中的相同指针。它在汇编级别是相同的地址,它可能会释放相同数量的内存——或者,你真的知道吗?你愚弄了编译器删除了一个 void* 而不是实际类型。
两个例子都是未定义的行为。现在我已经花时间浏览了 C++17 standard final draft,我找到了我需要的证据。
示例 A
关于operator new
:
分配函数 - § 6.7.4.1.2
If the request succeeds, the value returned shall be a
non-null pointer value (7.11) p0 different from any previously returned value p1, unless that value p1 was
subsequently passed to an operator delete
在示例 A 中,我们调用 new-expression、Simple* a = new Simple()
,它在内部将调用适当的 operator new
。我们在调用 UserImplementedFree(static_cast<void*>(a))
时绕过 operator delete
。尽管 operator delete
会调用此函数,并且可能会执行相同的释放操作,但要注意的是,对 operator new
的任何后续调用现在都可能 return 一个与 [=19] 匹配的地址的指针=] 有。 a
从未传递给 operator delete
。所以我们违反了上述规则。
示例 B
删除表达式 - § 8.3.5.2
...the value of the operand of delete may be a null pointer
value, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject (4.5)
representing a base class of such an object (Clause 13). If not, the behavior is undefined. In the second
alternative (delete array), the value of the operand of delete may be a null pointer value or a pointer
value that resulted from a previous array new-expression.
83 If not, the behavior is undefined.
在示例 B 中,我们不通过 new-expression 分配 addr
。然后我们尝试使用 delete-expression 来释放它。这违反了上述规则。
定义的行为是什么样的?
这些例子的主要特点是构造和分配的分离,以及销毁和释放的分离。该标准规定如下:
新表达式 - § 8.3.4.11
For arrays of char
, unsigned char
,
and std::byte
, the difference between the result of the new-expression and the address returned by the
allocation function shall be an integral multiple of the strictest fundamental alignment requirement (6.11) of
any object type whose size is no greater than the size of the array being created. [ Note: Because allocation
functions are assumed to return pointers to storage that is appropriately aligned for objects of any type with
fundamental alignment, this constraint on array allocation overhead permits the common idiom of allocating
character arrays into which objects of other types will later be placed. — end note ]
所以定义的行为可能看起来像这样:
{
//Allocates bytes
char* bytes = new char[sizeof(Simple)];
//Invokes constructor
Simple* a = new ((void *)bytes) Simple();
//Invokes destructor
a->~Simple();
//Deallocates
delete[] bytes;
}
再说一次,不一定是好的做法,而是明确的行为。
在 SL operator new
未返回的非空指针上调用 SL operator delete
被视为未定义行为,如此处针对 (1) 和 (2) 所述:
https://en.cppreference.com/w/cpp/memory/new/operator_delete
The behavior of the standard library implementation of this function is undefined unless ptr is a null pointer or is a pointer previously obtained from the standard library implementation of
operator new(size_t)
oroperator new(size_t, std::nothrow_t)
.
因此,混合使用 operator new
、operator delete
和 operator new[]
、operator delete[]
也是未定义的行为。我在标准中找不到任何说明这是否也适用于调用用户分配方法的替换 operator new
和 operator delete
。例如:
void* operator new(std::size_t p_size)
{
void *ptr = UserImplementedAlloc(p_size);
return ptr;
}
void* operator new[](std::size_t p_size)
{
void *ptr = UserImplementedAlloc(p_size);
return ptr;
}
void operator delete(void* p_ptr)
{
UserImplementedFree(p_ptr);
}
void operator delete[](void* p_ptr)
{
UserImplementedFree(p_ptr);
}
以下是否未定义?假设 UserImplementedAlloc
总是 returns 一个正确的地址,而不是 nullptr
.
struct Simple
{
explicit Simple(); //Allocates m_bytes
~Simple(); //Frees m_bytes
char * m_bytes;
};
/*Placement new is not replaced or overridden for these examples.*/
//Example A
{
//Allocates and invokes constructor
Simple* a = new Simple();
//Invokes destructor
a->~Simple();
//Deallocates
UserImplementedFree(static_cast<void*>(a));
}
//Example B
{
//Allocates
void* addr = UserImplementedAlloc(sizeof(Simple));
//Invokes constructor
Simple* b = new (addr) Simple();
//Invokes destructor and deallocates
delete b;
}
我不是在寻找关于这是否是不良做法的讲座,我只是想确定这是否是定义的行为。
您的编译器版本的 delete 可能知道一些隐藏在实现中的关于依赖于类型的已删除指针的信息。
首先手动调用析构函数然后删除一个 void*(因为否则你会调用析构函数两次)是不安全的。您没有删除 C++ 语义中的相同指针。它在汇编级别是相同的地址,它可能会释放相同数量的内存——或者,你真的知道吗?你愚弄了编译器删除了一个 void* 而不是实际类型。
两个例子都是未定义的行为。现在我已经花时间浏览了 C++17 standard final draft,我找到了我需要的证据。
示例 A
关于operator new
:
分配函数 - § 6.7.4.1.2
If the request succeeds, the value returned shall be a non-null pointer value (7.11) p0 different from any previously returned value p1, unless that value p1 was subsequently passed to an
operator delete
在示例 A 中,我们调用 new-expression、Simple* a = new Simple()
,它在内部将调用适当的 operator new
。我们在调用 UserImplementedFree(static_cast<void*>(a))
时绕过 operator delete
。尽管 operator delete
会调用此函数,并且可能会执行相同的释放操作,但要注意的是,对 operator new
的任何后续调用现在都可能 return 一个与 [=19] 匹配的地址的指针=] 有。 a
从未传递给 operator delete
。所以我们违反了上述规则。
示例 B
删除表达式 - § 8.3.5.2
...the value of the operand of delete may be a null pointer value, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject (4.5) representing a base class of such an object (Clause 13). If not, the behavior is undefined. In the second alternative (delete array), the value of the operand of delete may be a null pointer value or a pointer value that resulted from a previous array new-expression. 83 If not, the behavior is undefined.
在示例 B 中,我们不通过 new-expression 分配 addr
。然后我们尝试使用 delete-expression 来释放它。这违反了上述规则。
定义的行为是什么样的?
这些例子的主要特点是构造和分配的分离,以及销毁和释放的分离。该标准规定如下:
新表达式 - § 8.3.4.11
For arrays of
char
,unsigned char
, andstd::byte
, the difference between the result of the new-expression and the address returned by the allocation function shall be an integral multiple of the strictest fundamental alignment requirement (6.11) of any object type whose size is no greater than the size of the array being created. [ Note: Because allocation functions are assumed to return pointers to storage that is appropriately aligned for objects of any type with fundamental alignment, this constraint on array allocation overhead permits the common idiom of allocating character arrays into which objects of other types will later be placed. — end note ]
所以定义的行为可能看起来像这样:
{
//Allocates bytes
char* bytes = new char[sizeof(Simple)];
//Invokes constructor
Simple* a = new ((void *)bytes) Simple();
//Invokes destructor
a->~Simple();
//Deallocates
delete[] bytes;
}
再说一次,不一定是好的做法,而是明确的行为。