将结构转换为数组
Casting a Struct to an Array
这是一个严格的别名问题,因为编译器会因此导致任何优化顺序问题。
假设我在 struct XMFLOAT3
中有三个 public float
(与 this one 不同),我想转换为 float*
.这会让我遇到优化问题吗?
XMFLOAT3 foo = {1.0f, 2.0f, 3.0f};
auto bar = &foo.x;
bar[2] += 5.0f;
foo.z += 5.0f;
cout << foo.z;
我假设这将始终打印“13”。但是这段代码呢:
XMFLOAT3 foo = {1.0f, 2.0f, 3.0f};
auto bar = reinterpret_cast<float*>(&foo);
bar[2] += 5.0f;
foo.z += 5.0f;
cout << foo.z;
我认为这是合法的,因为根据 http://en.cppreference.com/w/cpp/language/reinterpret_cast#Type_aliasing
T2 is an aggregate type or a union type which holds one of the aforementioned types as an element or non-static member (including, recursively, elements of subaggregates and non-static data members of the contained unions): this makes it safe to cast from the first member of a struct and from an element of a union to the struct/union that contains it.
我的理解正确吗?
显然这将成为依赖于 XMFLOAT3
的声明的实现。
完全正确;这与严格别名无关。
严格的别名规则要求相互别名的指针具有兼容的类型;
显然,float*
与 float*
.
兼容
从 XMFLOAT3*
到 float*
的 reinterpret_cast
没问题,因为:
9.2 [class.mem] 第 20 段:
If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member. Otherwise, its address is the same as the address of its first base class
subobject (if any). [ Note: There might therefore be unnamed padding within a standard-layout struct object, but not at its beginning, as necessary to achieve appropriate alignment. — end note ]
这意味着第一个成员的地址是结构的地址,并且当您访问 *bar
时不涉及别名,因为您正在通过 float
类型的左值访问 float
,很好。
不过也不需要强制转换,相当于第一个版本:
auto bar = &foo.x;
表达式 bar[2]
只有在结构成员之间没有填充的情况下才可以,或者更准确地说,如果数据成员的布局与数组相同 float[3]
,在这种情况下,3.9.2 [basic.compound] 第 3 段表示可以:
A valid value of an object pointer type represents either the address of a byte in memory (1.7) or a null pointer (4.10). If an object of type T
is located at an address A
, a pointer of type cv T*
whose value is the
address A
is said to point to that object, regardless of how the value was obtained.
在实践中,没有理由不将相同类型的三个相邻非静态数据成员与数组布局相同(我认为 Itanium 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]");
Obviously this will become implementation dependent on the declaration of XMFLOAT3.
是的,它依赖于它是一个标准布局 class 类型,以及它的数据成员的顺序和类型。
考虑一个相当智能的编译器:
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
用已知结果代替变量访问和操作是一种常见的优化。在这里,优化器看到 .z
的三种用途:初始分配、增量和最终使用。它可以简单地确定这三个点的值,并替换它们。
因为结构成员不能重叠(与联合不同),从 .x
派生的 bar
不能重叠 .z
,所以 .bar[2]
不能影响 .z
。
如您所见,完全正常的优化器可以产生 "wrong" 结果。
这是一个严格的别名问题,因为编译器会因此导致任何优化顺序问题。
假设我在 struct XMFLOAT3
中有三个 public float
(与 this one 不同),我想转换为 float*
.这会让我遇到优化问题吗?
XMFLOAT3 foo = {1.0f, 2.0f, 3.0f};
auto bar = &foo.x;
bar[2] += 5.0f;
foo.z += 5.0f;
cout << foo.z;
我假设这将始终打印“13”。但是这段代码呢:
XMFLOAT3 foo = {1.0f, 2.0f, 3.0f};
auto bar = reinterpret_cast<float*>(&foo);
bar[2] += 5.0f;
foo.z += 5.0f;
cout << foo.z;
我认为这是合法的,因为根据 http://en.cppreference.com/w/cpp/language/reinterpret_cast#Type_aliasing
T2 is an aggregate type or a union type which holds one of the aforementioned types as an element or non-static member (including, recursively, elements of subaggregates and non-static data members of the contained unions): this makes it safe to cast from the first member of a struct and from an element of a union to the struct/union that contains it.
我的理解正确吗?
显然这将成为依赖于 XMFLOAT3
的声明的实现。
完全正确;这与严格别名无关。
严格的别名规则要求相互别名的指针具有兼容的类型;
显然,float*
与 float*
.
从 XMFLOAT3*
到 float*
的 reinterpret_cast
没问题,因为:
9.2 [class.mem] 第 20 段:
If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member. Otherwise, its address is the same as the address of its first base class subobject (if any). [ Note: There might therefore be unnamed padding within a standard-layout struct object, but not at its beginning, as necessary to achieve appropriate alignment. — end note ]
这意味着第一个成员的地址是结构的地址,并且当您访问 *bar
时不涉及别名,因为您正在通过 float
类型的左值访问 float
,很好。
不过也不需要强制转换,相当于第一个版本:
auto bar = &foo.x;
表达式 bar[2]
只有在结构成员之间没有填充的情况下才可以,或者更准确地说,如果数据成员的布局与数组相同 float[3]
,在这种情况下,3.9.2 [basic.compound] 第 3 段表示可以:
A valid value of an object pointer type represents either the address of a byte in memory (1.7) or a null pointer (4.10). If an object of type
T
is located at an addressA
, a pointer of type cvT*
whose value is the addressA
is said to point to that object, regardless of how the value was obtained.
在实践中,没有理由不将相同类型的三个相邻非静态数据成员与数组布局相同(我认为 Itanium 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]");
Obviously this will become implementation dependent on the declaration of XMFLOAT3.
是的,它依赖于它是一个标准布局 class 类型,以及它的数据成员的顺序和类型。
考虑一个相当智能的编译器:
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
用已知结果代替变量访问和操作是一种常见的优化。在这里,优化器看到 .z
的三种用途:初始分配、增量和最终使用。它可以简单地确定这三个点的值,并替换它们。
因为结构成员不能重叠(与联合不同),从 .x
派生的 bar
不能重叠 .z
,所以 .bar[2]
不能影响 .z
。
如您所见,完全正常的优化器可以产生 "wrong" 结果。