重新诠释联盟与不同的工会
Reinterpreting a union to a different union
我有一个标准的layout联合,其中有很多类型:
union Big {
Hdr h;
A a;
B b;
C c;
D d;
E e;
F f;
};
A
thru F
中的每种类型都是标准layout,它是其第一个成员Hdr
类型的对象。Hdr
标识了联盟的活跃成员,因此这是类似变体的。现在,我正在确定(因为我检查过(活动成员是B
或C
。有效地,我将空间缩小到:
union Little {
Hdr h;
B b;
C c;
};
现在,以下定义明确或未定义的行为?
void given_big(Big const& big) {
switch(big.h.type) {
case B::type: // fallthrough
case C::type:
given_b_or_c(reinterpret_cast<Little const&>(big));
break;
// ... other cases here ...
}
}
void given_b_or_c(Little const& little) {
if (little.h.type == B::type) {
use_a_b(little.b);
} else {
use_a_c(little.c);
}
}
Little
的目标是有效地用作文档,我已经检查了它是B
或C
,因此将来没有人添加代码来检查它是A
或其他东西。
我将B
子对象读成B
的事实是否足以使这种形成良好?可以在此处有意义地使用常见的初始序列规则吗?
能够将指针置于a,并将其重新解释为b的指针,必须是 pointer-interconvertible 。
指针交换性是关于对象,而不是对象的类型。
在C 中,某个地方有对象。如果您在特定位置有一个Big
,至少有一个成员存在,则由于指针互连而在同一位置也有一个Hdr
。
但是,该位置没有Little
对象。如果那里没有Little
对象,则不能使用不存在的Little
对象的指针交换。
假设它们是平面数据(普通的旧数据,可复制等(。
这意味着您可以复制他们的字节表示,并且可以使用。实际上,优化器似乎了解到一个堆栈本地缓冲区的纪念品,一个新的位置(带有琐碎的构造函数(,然后一个emcpy后退实际上是一个不p的。
template<class T>
T* laundry_pod( void* data ) {
static_assert( std::is_pod<Data>{}, "POD only" ); // could be relaxed a bit
char buff[sizeof(T)];
std::memcpy( buff, data, sizeof(T) );
T* r = ::new( data ) T;
std::memcpy( data, buff, sizeof(T) );
return r;
}
上面的功能是运行时的NOOP(在优化的构建中(,但它将data
的T-Layout兼容数据转换为实际的T
。
因此,如果我是对的,并且当Big
是Little
中类型的子类型时,Big
和Little
与布局兼容,则可以这样做:
Little* inplace_to_little( Big* big ) {
return laundry_pod<Little>(big);
}
Big* inplace_to_big( Little* big ) {
return laundry_pod<Big>(big);
}
或
void given_big(Big& big) { // cannot be const
switch(big.h.type) {
case B::type: // fallthrough
case C::type:
auto* little = inplace_to_little(&big); // replace Big object with Little inplace
given_b_or_c(*little);
inplace_to_big(little); // revive Big object. Old references are valid, barring const data or inheritance
break;
// ... other cases here ...
}
}
如果Big
具有非静电数据(例如参考或const
数据(,则以上会发生可怕的。
请注意,laundry_pod
不执行任何内存分配;它使用新的位置,该位置在data
点使用data
处的CC_28点的位置构建T
。虽然它看起来像在做很多事情(复制内存(,但它优化为NOOP。
c 具有"对象存在"的概念。对象的存在几乎与物理或抽象机器中写入的碎屑或字节几乎无关。您的二进制中没有指示与"现在存在的对象"相对应。
但是语言有这个概念。
不存在的对象无法与之相互作用。如果这样做,C 标准将无法定义程序的行为。
这允许优化器对您的代码所做的工作,不做什么,以及无法达到哪些分支的假设以及可以达到哪些分支。它让编译器做出不明的假设;通过指针或引用A 无法通过指针或引用B进行的数据修改数据,除非A和B以某种方式存在于同一地点。
编译器可以证明Big
和Little
对象都不能同时存在。因此,没有通过指针或对Little
引用的任何数据修改任何数据可以修改Big
类型变量中存在的任何内容。反之亦然。
想象一下given_b_or_c
是否修改了字段。好吧,编译器可以在given_big
和given_b_or_c
和use_a_b
上进行内联,请注意,没有修改Big
的实例(只是Little
的一个实例(,并证明Big
的数据字段在调用您的代码之前被拨打了您的代码之前,无法修改。
Big b = whatever;
b.foo = 7;
((Little&)b).foo = 4;
if (b.foo!=4) exit(-1);
最佳地
Big b = whatever;
b.foo = 7;
((Little&)b).foo = 4;
exit(-1);
因为它可以证明b.foo
必须是7
,它已设置为一次,并且永远不会修改。通过Little
的访问无法修改Big
,因此由于异叠性规则。
现在这样做:
Big b = whatever;
b.foo = 7;
(*laundry_pod<Little>(&b)).foo = 4;
Big& b2 = *laundry_pod<Big>(&b);
if (b2.foo!=4) exit(-1);
,假设那里的大大是没有变化的,因为有一个可疑的和一个::new
可以合法地改变数据状态。没有严格的混叠违规。
它仍然可以遵循memcpy
并消除它。
laundry_pod
的实时示例被优化。请注意,如果未进行优化,则代码必须具有条件和printf。但是因为是,它已被优化到空程序中。
我在N4296(C 14标准(中找不到措辞,这将使这一合法。更重要的是,我无法找到任何给定的措辞:
union Big2 {
Hdr h;
A a;
B b;
C c;
D d;
E e;
F f;
};
我们可以将Big
引用到Big2
的引用中,然后使用参考。(请注意, Big
和 Big2
是是 bitut-compatible 。(
这是省略的ub。[expr.ref]/4.2:
如果E2是非静态数据成员,并且E1的类型为"
cq1 vq1 X
",则 E2的类型为"cq2 vq2 T
",表达式[E1.E2
]指定 命名为第一个表达式指定对象的成员。
在评估given_b_or_c
given_big
中的呼叫时,little.h
中的对象表达式实际上并未指定Little
对象,并且Ergo没有这样的成员。由于标准"省略了这种情况的任何明确定义",因此该行为是不确定的。
我不确定,如果这确实适用。在reinterpret_cast
-注释部分中,他们谈论了指针交换对象。
和[basic.compound]/4:
两个对象 a 和 b 是 pointer-interconvertible if:
- 它们是同一对象,或
- 一个是联合对象,另一个是该对象的非静态数据成员,或者
- 一个是标准的类对象,另一个是该对象的第一个非静态数据成员,或者,如果对象没有非静态数据成员,则该对象的第一个基类子对象或
- 存在一个对象 c ,因此 a 和 c 是指针交换,以及 c 和 b 是指针交换。
如果两个对象是指针交换的,则它们具有相同的地址,并且可以通过
reinterpret_cast
从一个指针获得一个指针。
在这种情况下,我们将Hdr h;
(c(作为两个工会中的非静态数据成员
Big* (a) -> Hdr* (c) -> Little* (b)
- 使用新行和不使用新行读取文件
- 如何在选项卡视图Qt中设置一个新项目,并保存以前的项目
- 在C++中,是否可以基于给定的标识符创建基类的新实例,反之亦然
- 我们可以访问一个不存在的联盟的成员吗
- 遇到新行时,有没有办法停止istream_iterator
- Constexpr替代了新的放置方式,可以让内存中的对象保持未初始化状态
- 当一个新对象被分配到它的地址时,对象是否必须被销毁
- 模板元编程:如何将参数包组合成新的参数包
- 使用不同的CRT将新的C++代码与旧的(二进制)组件隔离开来的最佳方法是什么
- 如何使用CLion在Mac上创建一个新的.txt文件
- 错误-我无法在VS2019中打开新的Qt项目
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践
- 为什么新的随机库比std::rand()更好
- 在cygwin中测试新的boost安装时出现cpp错误
- C++:继续创建新的变量可以吗
- Qt:当QListView获得新条目时,如何更新QStringList
- 为C++03编译器编写部分unique_ptr,该编译器与较新的编译器在公共代码库上运行
- c++20[[no.unique_address]]中的新功能是什么
- 将指针类分配给新类,C++
- 重新诠释联盟与不同的工会