C++ 将结构强制转换为数组

C++ 将结构强制转换为数组,c++,arrays,struct,alias,reinterpret-cast,C++,Arrays,Struct,Alias,Reinterpret Cast,这是一个严格的别名问题,因为编译器会因此导致任何优化顺序问题 假设我在结构XMFLOAT3中有三个公共浮点s(与之类似),我想转换为浮点*。这会给我带来麻烦吗 XMFLOAT3 foo = {1.0f, 2.0f, 3.0f}; auto bar = &foo.x; bar[2] += 5.0f; foo.z += 5.0f; cout << foo.z; xmfloat3foo={1.0f,2.0f,3.0f}; 自动栏=&foo.x; 棒[2]+=5.0f; foo.

这是一个严格的别名问题,因为编译器会因此导致任何优化顺序问题

假设我在
结构XMFLOAT3
中有三个公共
浮点
s(与之类似),我想转换为
浮点*
。这会给我带来麻烦吗

XMFLOAT3 foo = {1.0f, 2.0f, 3.0f};
auto bar = &foo.x;

bar[2] += 5.0f;
foo.z += 5.0f;
cout << foo.z;
xmfloat3foo={1.0f,2.0f,3.0f};
自动栏=&foo.x;
棒[2]+=5.0f;
foo.z+=5.0f;

cout完全有效;这与严格的别名无关

严格的别名规则要求相互别名的指针具有兼容的类型

显然,
float*
float*

兼容,从
XMFLOAT3*
float*
重新解释转换是正常的,因为:

9.2[类别mem]第20段:

如果标准布局类对象具有任何非静态数据成员,则其地址与其第一个非静态数据成员的地址相同。否则,其地址与第一个基类的地址相同 子对象(如果有)。[注:因此,标准布局结构对象中可能会有未命名的填充,但不会在其开头,这是实现适当对齐所必需的。-结束注]

这意味着第一个成员的地址就是结构的地址,当您访问
*bar
时,不涉及别名,因为您是通过
float
类型的左值访问
float
,这很好

但是演员阵容也没有必要,它相当于第一版:

auto bar = &foo.x;
表达式
bar[2]
只有在结构成员之间没有填充时才可以,或者更准确地说,如果数据成员的布局与数组
float[3]
相同,则表达式
bar[2]
,在这种情况下,3.9.2[basic.component]第3段表示可以:

对象指针类型的有效值表示内存中字节的地址(1.7)或空指针(4.10)。如果
T
类型的对象位于地址
A
,则为cv
T*
类型的指针,其值为 地址
A
被称为指向该对象,而不管该值是如何获得的

实际上,没有理由认为同一类型的三个相邻非静态数据成员的布局不与阵列相同(我认为安腾ABI可以保证这一点),但为了安全起见,您可以添加:

 static_assert(sizeof(XMFLOAT3)==sizeof(float[3]),
     "XMFLOAT3 layout must be compatible with float[3]");
或者是偏执狂,或者如果在
z
之后只有额外的成员:

 static_assert(offsetof(XMFLOAT3, y)==sizeof(float)
               && offsetof(XMFLOAT3, z)==sizeof(float)*2,
     "XMFLOAT3 layout must be compatible with float[3]");
显然,这将成为依赖于XMFLOAT3声明的实现


是的,它依赖于它是一个标准布局类类型,以及它的数据成员的顺序和类型。

考虑一个相当智能的编译器:

XMFLOAT3 foo = {1.0f, 2.0f, 3.0f}; 
auto bar = &foo.x;

bar[2] += 5.0f;
foo.z += 5.0f; // Since no previous expression referenced .z, I know .z==8.0
cout << foo.z; // So optimize this to a hardcoded cout << 8.0f
xmfloat3foo={1.0f,2.0f,3.0f};
自动栏=&foo.x;
棒[2]+=5.0f;
foo.z+=5.0f;//因为之前没有表达式引用.z,所以我知道.z==8.0

我是否可以更具体地认为问题是:“XMFLOAT3*
是否与
float*
兼容?”结构是否可能包含填充?从编译器到放入实际的填充是一种迂回的行为,但是优化器可能会假定正确的代码无法检测到这种填充。另外,
(&foo.x)[2]
看起来像是一个纯粹的越界数组访问,这对编译器来说是显而易见的。@MSalters,是的,可能有填充,尽管在理论上和实践中,填充只会出于对齐目的而添加,并且三个相邻的float成员的对齐方式与
float[3]的三个元素相同
,所以这确实是一种迂回。
(&foo.x)[2]
相当于
*(&foo.x+2)
,3.9.2/3使其格式良好,只要该地址确实有一个
float
,它再次返回到填充和对齐。@MSalters似乎可以使用中的
static\u assert
来保护编译器不受头脑混乱的影响。(无意冒犯啮齿动物。)@JonathanWakely:“这可能是为了对齐”并不是一个详尽的列表。如果只是为了对齐而允许填充,则该语句大致为“不得有初始填充。除满足填充后构件的对齐要求外,不得在其他任何地方进行填充”@JonathanMee为该索引添加了一个来自第一个成员thing的副本。您描述的是这就是问题所在,但我相信编译器不允许对
bar[2]+=5.0fcout
后面加上code>,因为我在问题中引用了。不,我所描述的是越界数组访问。严格的别名是当
union{int x;float z}foo
(&foo.x)[0]
foo.z
重叠时。这不再是一种越界访问,但现在它变成了一种严格的别名冲突。要发生严格的别名,首先需要一个有效的表达式来引用内存,该内存包含另一个不兼容类型的对象
bar[2]
根本无效。@JonathanMee:至于“重新排序”,我假设编译器优化了对
bar[2]
的整个赋值,因为没有更多的代码依赖于
bar
x
的值。为什么3.9.2[basic.compound]第3段不适用于
bar[2]
?注释似乎非常相关:“[注:例如,超过数组(5.7)末尾的地址将被视为指向数组元素类型的一个不相关对象,该对象可能位于该地址…”你的意思是可以用一个元素而不是两个元素越界吗?@JonathanWakely:在允许对象之后直接生成地址。但您可能无法读取或写入该地址,这仍然是一种越界访问+2是正确的。关于
静态断言的极好建议。我很感激你的提醒。在Meta上,这篇文章
XMFLOAT3 foo = {1.0f, 2.0f, 3.0f}; 
auto bar = &foo.x;

bar[2] += 5.0f;
foo.z += 5.0f; // Since no previous expression referenced .z, I know .z==8.0
cout << foo.z; // So optimize this to a hardcoded cout << 8.0f