reinterpret_cast创建一个简单的默认构造对象

Bar*_*rry 51 c++ language-lawyer c++11 c++17

cppreference 声明:

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

这意味着以下是明确定义的代码:

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

以下是三个问题:

  1. 这个引用是否正确?
  2. 如果是,那么X开始的生命周期是什么时候?如果在线(B),它是否被视为获取存储?如果在线(A),如果有一个分支(A)(B)有条件地构建一个X或其他一些pod,Y怎么办?
  3. 在这方面,C++ 11和C++ 1z之间有什么变化吗?

请注意,这是一个旧链接.针对这个问题,措辞发生了变化.它现在写道:

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

T.C*_*.C. 33

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

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

一个目的是通过一个定义创建([basic.def]),通过一个 新的表达式([expr.new])中,当隐式地改变联合的活性件([class.union]),或者当一个临时对象创建([conv.rval],[class.temporary]).

采用P0137R1时,本段是"对象"一词的定义.

是否有X对象的定义?没有.有新表达吗?没有.有工会吗?没有.您的代码中是否有一个语言结构可以创建一个临时X对象?没有.

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

C++ 11具有大致相同的段落,但不将其用作"对象"的定义.尽管如此,解释是一样的.另一种解释 - 在获得合适的存储空间后立即将[basic.life]视为创建对象 - 意味着您正在创建Schrödinger的对象*,这与N3337 [intro.object]/6相矛盾:

如果一个是另一个的子对象,或者如果至少一个是零大小的基类子对象并且它们是不同类型的,则不是位字段的两个对象可以具有相同的地址; 否则,他们应有不同的地址.


*对于类型T,具有适当对齐和尺寸的存储是按照定义存储的,具有适当的对齐和尺寸,适用于尺寸和对齐要求等于或小于的尺寸和对齐要求的其他类型T.因此,该解释意味着同时获得存储在所述存储器中创建具有不同类型的无限对象集,所有对象都具有相同的地址.

  • @TC我将明确:`auto p_int =(int*)malloc(sizeof(int));*p_int = 0; std :: cout <<*p_int <<"\n";` - 任何不符合标准的东西应该是非首发的.这是传统的C风格内存处理,它存在于大量遗留代码库中,这些代码库已在C++中编译和使用了30多年.如果C++标准说"未定义",则标准中存在缺陷.我得到*为什么*这很难,但是保持模糊或措辞不好比明确说明未定义更好. (13认同)
  • @TC虽然我明白,无法使用`auto p_int =(int*)malloc(sizeof(int));`定义的行为似乎是一个非常糟糕的主意.我明白了,定义行为很难,但替代方案很糟糕.遗留代码的复归与从"实践工作"到"诅咒".如果标准不允许,标准是错误的; 修复它的方法是修复标准,而不是使标准的错误更明确. (6认同)
  • @Yakk IMO标准清晰,比模糊或措辞不好更好.然后讨论可以至少转向修复它而不是像这样无休止的线程,人们应用他们自己的解释,我们争论谁的解释[更好| 是意图| 等等.] (6认同)
  • @RichardHodges首先,脚注是非规范性的.其次,该脚注涉及"安全派生指针"的定义,这完全不相关 - 该概念适用于GC支持.第三,相当完善的是,单独的"malloc"不足以在当前措辞下创建一个对象--P0137明确地将其称为现状. (4认同)
  • @RichardHodges将脚注脱离背景无法帮助您解决问题.该脚注附于[basic.stc.dynamic.safety] /2.1,并且eventstance [basic.life]在该特定版本的工作草案的同一页面上开始."安全派生指针"仅与具有*严格指针安全性*(又名GC'd实现)的实现相关,这是一个空集AFAIK.它绝对不会对[basic.life]的含义有所了解,因为它涉及的是一个完全不同的主题. (3认同)
  • @Yakk每个标准都有相关的措辞.这当然是个问题,但却是一个长期存在的问题. (3认同)
  • @MM否; 如果使用`int_p`是明确的未定义行为,编译器团队中的一些白痴可能会破坏使用它的代码并让人们站在一边(毕竟,标准是明确的!).如果它需要复杂的推理而且模糊不正以证明这一点,那么其他人更有可能将他们当作白痴."澄清"`int_p`使用的任何东西都是非法的,要么是要改变要破坏的标准,要么抛光标准缺陷. (3认同)
  • @Barry Code依赖于解除引用空指针的行为是一个滴答作响的定时炸弹,无论如何迟早会出现这个问题.我认为gcc从未明确支持解除引用空指针的已定义行为 - 你得到的只是偶然事件.就用户群而言,那些想要解除引用null的定义行为的人与那些不希望他们的代码因插入运行时空指针检查而变慢的人之间存在冲突(等等).但在这种情况下,我没有看到任何类似的冲突. (3认同)
  • 如果是这种情况,那么最大的尊重(这不是舌头,你的答案是非常有用的),然后通过malloc分配一个对象将是未定义的行为.然而§3.8明确允许它.该标准的措辞似乎有所脱节. (2认同)
  • @Yakk好吧,无论如何,对于读取未初始化的对象来说,这都是不确定的:)当前的事务状态肯定不是最佳的-正式的对象模型使`std :: vector`在标准C ++中无法实现-但使其正常工作并非易事。 (2认同)
  • @TC 现在它是 C++20 中定义的行为。根据 [[intro.object](https://eel.is/c++draft/intro.object#13)]/13,开始字符数组的生命周期会在该数组占用的存储中隐式创建另一个对象数组,前提是另一个对象是隐式生命周期类型。是时候更新答案了。 (2认同)

Ami*_*rsh 7

基于p0593r6,我相信OP中的代码是有效的并且应该被很好地定义。新的措辞基于追溯应用于 C++98(含)所有版本的 DR,允许隐式对象创建,只要创建的对象定义良好(重言式有时可以解决复杂定义),请参阅 \xc2\xa7 6.7 .2.11 对象模型[intro.object] ):

\n\n
\n

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

\n
\n\n

另请参阅:/sf/answers/4339940601/

\n