取消分配由全局替换运算符new返回的指针而不调用替换运算符delete的未定义行为?(C ++ 17)

史丹利

operator delete在未由SL返回的非空指针上调用SL被视为未定义行为operator new,如此处针对(1)和(2)所述:https : //en.cppreference.com/w/cpp/memory/ new / operator_delete

除非ptr是空指针或者是先前从operator new(size_t)or的标准库实现获得的指针,否则未定义此函数的标准库实现的行为operator new(size_t, std::nothrow_t)

因此,它也未定义行为混合的用途operator newoperator deleteoperator 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始终返回正确的地址,而不返回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;
}

我不是在寻找关于这是否不好的方法的讲座,我只是在试图确定这是否是定义的行为。

史丹利

这两个示例都是未定义的行为。现在,我花了时间浏览C ++ 17标准最终草案,我找到了我需要的证据。


例子A

关于operator new

分配功能-§6.7.4.1.2

如果请求成功,则返回的值应为不同于任何先前返回的值p1的非空指针值(7.11)p0,除非随后将该值p1传递给 operator delete

实例A我们称之为新表达Simple* a = new Simple(),在内部将调用适当的operator new我们operator delete在致电时会绕过UserImplementedFree(static_cast<void*>(a))即使operator delete将调用此函数并可能执行相同的取消分配,但要注意的是,对的任何后续调用operator new现在都可能返回与所拥有地址匹配的指针a并且a从未被传给operator delete因此,我们违反了上述规则。


例子B

delete-expression-第8.3.5.2节

... delete的操作数的值可以是空指针值,指向由先前的new表达式创建的非数组对象的指针或指向表示该对象基类的子对象(4.5)的指针(第13条)。如果不是,则行为是不确定的。在第二个替代方案(删除数组)中,删除操作数的值可以是空指针值,也可以是由先前数组new-expression产生的指针值。83如果不是,则行为未定义。

在示例B中,我们不addr通过new-expression进行分配然后,我们尝试使用delete-expression对其进行分配。违反了以上规则。


定义的行为是什么样的?

这些示例的主要特征是将构造与分配分离,以及将破坏与重新分配分离。该标准规定以下内容:

new-expression -§8.3.4.11

对于,,和的数组charnew表达式的结果与分配函数返回的地址之间的差应为大小不大于的任何对象类型的最严格基本对齐要求(6.11)的整数倍。正在创建的数组的大小。[注意:因为假定分配函数将指针返回到针对具有基本对齐方式的任何类型的对象而适当对齐的存储,所以这种对数组分配开销的约束允许将字符数组分配给其他类型的对象,稍后再将其放入其中—尾注]unsigned charstd::byte

因此,定义的行为可能看起来像这样:

{
    //Allocates bytes
    char* bytes = new char[sizeof(Simple)];
    //Invokes constructor
    Simple* a = new ((void *)bytes) Simple();
    //Invokes destructor
    a->~Simple();
    //Deallocates
    delete[] bytes;
}

再一次,不一定是好的做法,而是明确的行为。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

Related 相关文章

热门标签

归档