【问题标题】:After placement-new into buffer, do buffer and instance have same void* address?将新放置到缓冲区后,缓冲区和实例是否具有相同的 void* 地址?
【发布时间】:2018-06-18 05:17:28
【问题描述】:

请看下面的代码:

unsigned char* p = new unsigned char[x];
CLASS* t = new (p) CLASS;
assert((void*)t == (void*)p);

我可以假设(void*)t == (void*)p吗?

【问题讨论】:

  • AFAIK 是,如果 T 不是数组。
  • C++规范中有定义保证吗?
  • 相关,可能重复? stackoverflow.com/questions/47653305/…
  • 不同的是,在你的链接中,它比较了2个类实例指针,在这篇文章中,它想比较原始缓冲区地址和新放置类实例指针地址。
  • 我记得对象的普通放置新函数被定义为只返回参数指针。

标签: c++ c++11 language-lawyer placement-new


【解决方案1】:

是的,你可以。我相信有几条规定可以保证这一点。

  1. [expr.new]/10 - 强调我的

    new-expression 将请求的空间量传递给 分配函数作为 std::size_t 类型的第一个参数。 那个 参数不得小于被创建对象的大小; 它可能大于正在创建的对象的大小,仅当 该对象是一个数组。对于 char 和 unsigned char 数组, new-expression 的结果与地址之间的差异 分配函数返回的应该是整数倍 最严格的基本对齐要求([basic.align]) 对象类型,其大小不大于所要数组的大小 创建的。 [注:因为分配函数假定返回 指向为任何对象适当对齐的存储的指针 具有基本对齐的类型,对数组分配的这种约束 开销允许将字符数组分配到 稍后将放置哪些其他类型的对象。 ——尾注]

    在我看来,新表达式必须在分配函数返回的确切地址处创建一个对象(假设它不是数组类型)。由于您使用的是内置的placement new,这将我们带到以下

  2. [new.delete.placement]

    这些函数是保留的,C++程序可能没有定义函数 取代标准 C++ 库中的版本 ([约束])。 ([basic.stc.dynamic]) 的规定不适用 到 operator new 和 operator delete 的这些保留放置形式。

    void* operator new(std::size_t size, void* ptr) noexcept;
    

    返回:ptr。

    备注:故意不做其他动作。

    这保证您传递给表达式的地址是您分配的字符数组对象的确切地址。这是因为转换为void* 不会更改源地址。

我认为保证地址相同就足够了,即使指针通常不可互换。所以根据[expr.eq]/1(感谢@T.C.):

两个相同类型的指针比较相等当且仅当它们是 都为null,都指向同一个函数,或者都代表同一个 地址([basic.compound])。

比较结果必须为真,因为地址相同。

【讨论】:

  • MSVC 在创建带有新位置的数组时似乎返回了一个不同的指针:stackoverflow.com/questions/15254/…
  • @HolyBlackCat - 是的,这是我不想踏入的黑暗角落。如果T 原来是一个数组,我将删除它。
  • 我认为您无论如何都不应该删除它,这是一个很好的答案。我还发现:[expr.new] 1 "如果实体是非数组对象,则 new 表达式返回指向创建的对象的指针。如果是数组,则 new 表达式返回指针到数组的初始元素。” 这似乎允许 MSVC 对数组的奇怪处理。
  • @HolyBlackCat - 我认为第一个引用段落中的注释解释了原因。这是为了让char 数组“与任何对象对齐”,正如operator new 返回的内存所假定的那样。
  • 最后,[expr.eq]/2.2 说,除了这里不相关的例外,如果两个指针表示相同的地址,则它们比较相等(即使它们的值不同,就像这里的情况一样)。
【解决方案2】:

我可以假设(void*)t == (void*)p吗?

不一定。

如果类的作者重载CLASS::operator new(size_t, unsigned char*),例如,该运算符可以返回除第二个参数以外的任何内容,例如:

struct CLASS
{
    static void* operator new(size_t, unsigned char* p) { return p + 1; }
};

如果您希望这个新表达式调用the standard non-allocating placement new operator,代码需要

  1. 包括标题<new>
  2. 确保将void* 参数传递给它。
  3. 使用范围解析运算符 :: 作为前缀以绕过 CLASS::operator new(如果有)。

例如:

#include <new> 
#include <cassert> 

unsigned char p[sizeof(CLASS)];
CLASS* t = ::new (static_cast<void*>(p)) CLASS;
assert(t == static_cast<void*>(p));

在这种情况下确实是t == static_cast&lt;void*&gt;(p)

事实上,这就是 GNU C++ 标准库所做的:

template<typename _T1, typename... _Args>
inline void _Construct(_T1* __p, _Args&&... __args) { 
    ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); 
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-13
    • 2021-09-29
    • 1970-01-01
    • 2021-02-20
    • 2022-11-11
    • 1970-01-01
    • 2020-02-26
    • 1970-01-01
    相关资源
    最近更新 更多