【问题标题】:reinterpret_cast creating a trivially default-constructible objectreinterpret_cast 创建一个简单的默认构造对象
【发布时间】:2017-04-13 21:36:00
【问题描述】:

cppreference 声明:

可以通过在任何适当对齐的存储上使用reinterpret_cast 创建具有琐碎默认构造函数的对象,例如在使用std::malloc 分配的内存上。

这意味着以下是定义良好的代码:

struct X { int x; };
alignas(X) char buffer[sizeof(X)];    // (A)
reinterpret_cast<X*>(buffer)->x = 42; // (B)

以下三个问题:

  1. 该引用正确吗?
  2. 如果是,X 的生命周期从什么时候开始?如果在线(B),是演员本身被认为是获取存储空间吗?如果在线(A),如果(A)(B) 之间有一个分支会有条件地构造一个X 或其他一些pod,Y
  3. 在这方面,C++11 和 C++1z 之间有什么变化吗?

请注意,这是一个旧链接。针对这个问题改变了措辞。现在是这样的:

然而,与 C 不同的是,不能通过简单地重新解释适当对齐的存储来创建具有微不足道的默认构造函数的对象,例如使用 std::malloc 分配的内存:placement-new 需要正式引入新对象并避免潜在的未定义行为。

【问题讨论】:

  • 我实际上试图弄清楚这些对象的生命周期何时开始的问题。我无法在标准中找到明确的答案,而且我相信,在这方面它是模糊的。至于第一个问题,我怀疑引用是否正确,因为需要注意一个别名规则。
  • @SergeyA 只要缓冲区是字符缓冲区,严格的别名就不是问题。
  • 不,我以为我们已经多次讨论过这个问题了? [intro.object]/1 详尽列举了哪些语言结构可以创建对象。
  • @RichardHodges,不。 char* 可以别名 anything,但 anything 不能别名 char*
  • @RichardHodges 实际上你可以使用(递归)联合,如果你想要constexpr,必须使用一个。

标签: c++ c++11 language-lawyer c++17


【解决方案1】:

没有X 对象,无论是活的还是其他的,所以假装有一个会导致未定义的行为。

[intro.object]/1 在创建对象时详细说明:

object 由定义([basic.def])创建,由 new-expression ([expr.new]),当隐式改变活动 联合的成员([class.union]),或者当一个临时对象是 已创建([conv.rval],[class.temporary])。

采用P0137R1,本段为“对象”一词的定义。

X 对象的定义吗?没有。有 new-expression 吗?没有。有工会吗?否。您的代码中是否存在创建临时X 对象的语言结构?没有。

无论 [basic.life] 对具有空初始化的对象的生命周期说什么都是无关紧要的。为此,您必须首先拥有一个对象。你没有。

C++11 有大致相同的段落,但不使用它作为“对象”的定义。尽管如此,解释是一样的。另一种解释——一旦获得合适的存储就将 [basic.life] 视为创建对象——意味着您正在创建薛定谔的对象*,这与 N3337 [intro.object]/6 相矛盾:

如果一个对象不是位域,则两个对象可能具有相同的地址 是另一个的子对象,或者如果至少一个是基类 大小为零的子对象,它们属于不同的类型;否则, 他们应该有不同的地址。


* 对于类型T 具有正确对齐和大小的存储,顾名思义,对于 所有其他类型 的大小具有正确对齐和大小的存储对齐要求等于或小于T。因此,该解释意味着获得存储同时在所述存储中创建具有不同类型的无限对象集,所有对象都具有相同的地址。

【讨论】:

  • @RichardHodges 首先,脚注是非规范性的。其次,该脚注与“安全派生指针”的定义有关,这是完全不相关的——该概念用于 GC 支持。第三,在当前措辞下,仅malloc 不足以创建对象,这是相当公认的——P0137 明确地将其称为现状。
  • @T.C.虽然我理解这一点,但无法使用auto p_int = (int*)malloc(sizeof(int)); 定义的行为似乎是一个非常糟糕的主意。我知道让它定义的行为很难,但另一种选择是可怕的。遗留代码从“在实践中工作”变成了“诅咒”。如果标准不允许这样做,则标准是错误的;修复它的方法是修复标准,而不是让标准的错误更明确。
  • @RichardHodges 断章取义地空运脚注对您的案子没有帮助。该脚注附在 [basic.stc.dynamic.safety]/2.1 之后,并且偶然 [basic.life] 在该特定版本的工作草案的同一页面上开始。 “安全派生的指针”仅与 strict pointer safety 的实现相关(又名 GC'd 实现),这是一个空集 AFAIK。它对 [basic.life] 的含义的理解绝对为零,因为它处理的是一个完全不同的主题。
  • @T.C.我会明确表示:auto p_int = (int*)malloc(sizeof(int)); *p_int = 0; std::cout &lt;&lt; *p_int &lt;&lt; "\n"; - 任何不符合该标准的东西都应该是非首发。那是遗留 C 风格的内存处理,它存在于大量遗留代码库中,这些代码库已经在 C++ 中编译和工作了 30 多年。如果 C++ 标准说“未定义”,那就是标准的缺陷。我明白了为什么这很难,但让它含糊不清或措辞不佳总比明确说明未定义要好。
  • @Yakk IMO 使标准清晰比模棱两可或措辞不佳要好。然后讨论至少可以转向修复它,而不是像这样无休止的线程,人们应用他们自己的解释,我们争论谁的解释[更好|更好]。是意图|等]
【解决方案2】:

基于p0593r6 我相信 OP 中的代码是有效的,应该定义良好。新措辞基于追溯适用于 C++98 (含)所有版本的 DR,允许隐式创建对象,只要创建的对象定义良好(tautology 有时是对复杂定义的拯救),请参阅 § 6.7.2.11 对象模型[intro.object]):

隐式创建的对象,其地址是起始地址 的存储区域,并产生一个指针值,指向 该对象,如果该值将导致程序已定义 行为 [...]

另见:https://stackoverflow.com/a/61999151/2085626

【讨论】:

    【解决方案3】:

    此分析基于 n4567,并使用其中的节号。

    §5.2.10/7:当对象指针类型的prvaluev转换为对象指针类型“pointer to cv T”时,结果为static_cast&lt;cv T*&gt;(static_cast&lt;cv void*&gt;(v))

    因此,在这种情况下,reinterpret_cast&lt;X*&gt;(buffer)static_cast&lt;X *&gt;(static_cast&lt;void *&gt;(buffer)) 相同。这导致我们查看有关static_cast的相关部分:

    §5.2.9/13:“指向 cv1 void 的指针”类型的纯右值可以转换为“指向 cv2 T 的指针”类型的纯右值,其中T 是一个对象类型,而 cv2 的 cv 限定与 cv1 相同或更高。空指针值转换为目标类型的空指针值。如果原始指针值代表内存中某个字节的地址A,并且A满足T的对齐要求,那么得到的指针值代表的地址与原始指针值相同,即A .

    我相信这足以说明原始报价是正确的——这种转换给出了明确的结果。

    至于生命周期,这取决于您所说的生命周期。强制转换创建了一个指针类型的新对象——一个临时对象,它的生命周期从强制转换所在的行开始,只要超出范围就结束。如果您有两个有条件地发生的不同转换,则每个指针的生命周期都从创建它的强制转换的位置开始。

    这些都不影响提供底层存储的对象的生命周期,它仍然是buffer,并且具有完全相同的生命周期,无论您是否创建指向该存储的指针(相同或转换类型)或不是。

    【讨论】:

    • 什么是结论?您是否声称指向 X 的指针是合法创建的,但实际上不能取消引用(例如,-&gt;x 是 UB),因为它们没有指向创建的 X 对象?目前尚不清楚指针本身的生命周期的相关性,也很难理解这个答案落在辩论的哪一边。
    • 是的,创建指针已经定义了行为,但是取消引用指针会给 UB。我认为他关于生命周期的问题有些模棱两可,所以我指出了代码中每个对象的生命周期,尽管我同意指针本身的生命周期可能不是他关心的。他询问了X 的生命周期,并没有涉及到实际的X,只是一个指向X 的指针,该指针使用chars 的缓冲区地址初始化。
    • 对,但最后,代码取消引用指针,就好像 有一个 X - 如果这不起作用(问题的症结所在,真的),也许指出来?
    • @BeeOnRope:我不敢这么说。现实情况是,它是官方未定义的行为,适用于每个已知的实现(对于这个词的几乎任何合理的定义),我希望它继续下去基本上永久工作。一个简单的事实是,打破这一点基本上会破坏所有 C 兼容性,我怀疑是否有编译器供应商愿意放弃它。
    • 很公平 - 正是那个“问题”导致我来到这里,因为我很难相信(例如)memcpy将一个可简单复制的类型放入合适的对齐未初始化存储中标准不允许,但这似乎是我们今天所处的位置:(。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-11-22
    • 2015-07-05
    • 1970-01-01
    • 2017-08-21
    • 1970-01-01
    • 1970-01-01
    • 2018-01-22
    相关资源
    最近更新 更多