C++ 取消分配全局替换运算符new返回的指针而不调用替换运算符delete是否是未定义的行为?(C+;+;17)
在SLC++ 取消分配全局替换运算符new返回的指针而不调用替换运算符delete是否是未定义的行为?(C+;+;17),c++,memory-management,new-operator,undefined-behavior,delete-operator,C++,Memory Management,New Operator,Undefined Behavior,Delete Operator,在SL运算符new未返回的非空指针上调用SL运算符delete被视为未定义的行为,如下(1)和(2)所述: 此函数的标准库实现的行为是未定义的,除非ptr是空指针或以前从运算符new(size\u t)或运算符new(size\u t,std::nothrow\u t)的标准库实现中获得的指针 因此,混合使用运算符new、运算符delete和运算符new[]、运算符delete[]也是未定义的行为。我在标准中找不到任何内容说明调用用户分配方法的替换操作员新建和操作员删除。例如: void* o
运算符new
未返回的非空指针上调用SL运算符delete
被视为未定义的行为,如下(1)和(2)所述:
此函数的标准库实现的行为是未定义的,除非ptr是空指针或以前从运算符new(size\u t)
或运算符new(size\u t,std::nothrow\u t)
的标准库实现中获得的指针
因此,混合使用运算符new
、运算符delete
和运算符new[]
、运算符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
始终返回正确的地址,而不是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;
}
struct Simple
{
explicit Simple();//分配m_字节
~Simple();//释放m_字节
字符*m_字节;
};
/*对于这些示例,Placement new不会被替换或覆盖*/
//例A
{
//分配和调用构造函数
简单*a=新的简单();
//调用析构函数
a->~Simple();
//转让
UserImplementedFree(静态_cast(a));
}
//例B
{
//分配
void*addr=UserImplementedAlloc(sizeof(Simple));
//调用构造函数
简单*b=新(添加)简单();
//调用析构函数并解除分配
删除b;
}
我不是在找关于这是否是一种不好的做法的讲座,我只是想确定这是否是定义的行为。您的编译器版本的delete可能知道一些隐藏在实现中的关于依赖于类型的已删除指针的信息
首先手动调用析构函数,然后删除void*(否则将调用析构函数两次)是不安全的。在C++语义中,您没有删除相同的指针。它在程序集级别是相同的地址,可能会释放相同数量的内存——或者,你真的知道吗?你愚弄了编译器,删除了一个void*,而不是实际的类型 这两个示例都是未定义的行为。现在我已经花了时间仔细检查了这些证据,我找到了我需要的证据
例A
关于新的操作员
:
分配功能-§6.7.4.1.2
如果请求成功,则返回的值应为
非空指针值(7.11)p0与之前返回的任何值p1不同,除非该值p1为
随后传递给操作员delete
在示例A中,我们调用一个新表达式,Simple*A=newsimple()
,它在内部将调用相应的运算符new
。调用UserImplementedFree(static_cast(a))
时,我们绕过了操作符delete
。尽管operator delete
将调用此函数,并可能执行相同的释放,但问题是,对operator new
的任何后续调用现在都可能返回与a
拥有的地址相匹配的指针。而a
从未传递给操作员delete
。所以我们违反了上述规则
例B
删除表达式-§8.3.5.2
…delete操作数的值可能是空指针
值、指向由以前的新表达式创建的非数组对象的指针或指向子对象的指针(4.5)
表示此类对象的基类(第13条)。如果不是,则行为未定义。第二
或者(delete array),delete操作数的值可以是空指针值或指针
由以前的数组和新表达式生成的值。
83如果不是,则行为未定义
在示例B中,我们不通过新表达式分配addr
。然后我们尝试使用delete表达式来取消分配它。这违反了上述规则
定义的行为会是什么样子?
这些示例的主要特征是构造与分配分离,销毁与释放分离。本标准规定如下:
新表达式-§8.3.4.11
对于字符
,无符号字符
的数组,
和std::byte
,新表达式的结果与
分配功能应为最严格的基本校准要求(6.11)的整数倍
大小不大于所创建数组大小的任何对象类型。[注:因为分配
函数被假定为返回指向存储的指针,该存储对于任何类型的具有
基本对齐,这种对数组分配开销的约束允许分配
其他类型的对象稍后将放入的字符数组-结束说明]
因此,定义的行为可能如下所示:
{
//Allocates bytes
char* bytes = new char[sizeof(Simple)];
//Invokes constructor
Simple* a = new ((void *)bytes) Simple();
//Invokes destructor
a->~Simple();
//Deallocates
delete[] bytes;
}
同样,不一定是良好的实践,而是明确的行为 >首先手动调用析构函数,然后删除void*——本例中没有删除void*。UserImplementedFree释放void*,但是没有delete表达式或对析构函数的调用,那么如何释放它呢?假设我们分配了一大块内存(例如通过malloc)当程序启动,然后UserImplementedAlloc和UserImplementedFree进入管理该内存块的应用程序级分配器。只有一个真正的分配,其余的是应用程序级分配。在您的示例中,您没有这样做。Y