联合或引用作为结构成员
Unions or references as struct members
是否可以保证对兄弟成员的引用将被优化掉?
Motivation:
struct using_union
{
union
{
std::uint32_t i;
std::uint8_t by[ 4 ];
} data;
};
struct using_alias
{
// shorter notation, no need for .data.i or .data.by
std::uint32_t i;
std::uint8_t (&by)[4] = reinterpret_cast< std::uint8_t(&)[4] >( i );
};
更一般地说,什么时候可以保证优化掉引用?是不是引用的生命周期比引用的生命周期短?
Is there any guarantee that references to sibling members will be optimized out?
不,没有这样的保证。
事实上,虽然可能无法以某种方式明确保证,但您几乎可以指望参考在实践中不会被优化。例如,如果创建一个引用不引用成员的实例,则此类优化将被破坏:
int another = 42;
using_alias an_example {
.i = 1337,
.by = another,
};
没有这样的保证。
注意严格的别名,std::uint8_t[4]
不是 reinterpret_cast
允许的别名类型之一。
此外,根本没有任何变通办法,因为如果您省略联合的名称,其成员将移至外部命名空间,即以下工作:
#include <cstdint>
struct using_union
{
union
{
std::uint32_t i;
std::uint8_t by[ 4 ];
};
};
int main(){
using_union a;
a.i=10;
}
但这仍然不允许您访问标准 C++ 中联合的任何非活动成员。
严格别名
reinterpret_cast
( cppreference.com)
有很多规则。随意查看 cppref,因为它比标准更友好。
相关部分
A glvalue of type T1, designating an object x, can be cast to the type
“reference to T2” if an expression of type “pointer to T1” can be
explicitly converted to the type “pointer to T2” using a
reinterpret_cast. The result is that of *reinterpret_cast<T2 *>(p)
where p is a pointer to x of type “pointer to T1”. No temporary is
created, no copy is made, and no constructors ([class.ctor]) or
conversion functions ([class.conv]) are called. [expr.reinterpret.cast][7.6.1.9.11]
An object pointer can be explicitly converted to an object pointer of
a different type.64 When a prvalue v of object pointer type is
converted to the object pointer type “pointer to cv T”, the result is
static_cast<cv T*>(static_cast<cv void*>(v)). [ Note: Converting a
prvalue of type “pointer to T1” to the type “pointer to T2” (where T1
and T2 are object types and where the alignment requirements of T2 are
no stricter than those of T1) and back to its original type yields the
original pointer value. — end note ] [expr.reinterpret.cast][7.6.1.9.7]
If a program attempts to access ([defns.access]) the stored value of
an object through a glvalue whose type is not similar ([conv.qual]) to
one of the following types the behavior is undefined
- (11.1) the dynamic type of the object,
- (11.2) a type that is the signed or unsigned type corresponding to the dynamic type of the object, or
- (11.3) a char, unsigned char, or std::byte type.
类型不相似,std::uint8_t[4]
不保证是以下任何一种类型 -> UB。我不认为 std::uint8_t(*)[4]
会衰减到 std::uint8_t*
,但我不是 100% 确定。无论哪种方式,它肯定是 UB,因为即使它确实衰减,std::uint8_t
也不同于 unsigned char
(但实际上它几乎总是如此)。
在 C++ 中处理这个问题的正确方法是 memcpy
联合类型双关语
查看 these 非常好的答案。
TLDR:在 C++ 中访问非活动成员是 UB,在 C 中明确允许。
如果你想检查值 reinterpret_cast<std::byte*>(&value)
是安全的。使用 memcpy
复制 to/from std::byte buffer[]
用于在两个不兼容的类型之间进行转换。不要担心性能,编译器可以发现这些模式。
看起来您正在尝试从 std::uint32_t
转换为 std::uint8_t
的某个数组,这将以小尾数法表示该值(因为这是在 x86 上使用的)。
问题是您的代码会根据平台字节顺序生成不同的结果。这就是为什么这段代码属于 C++ 未定义行为之一(两个版本)。
要在没有 UB 的情况下解决这个问题,您需要不同的代码,然后检查编译器如何处理它。
可能的 C++14 解决方案可能如下所示:
template<typename T, size_t ...indexes>
constexpr auto to_le_array_helper(T x, std::integer_sequence<size_t, indexes...>) ->
std::enable_if_t<std::is_integral_v<T>,
std::array<std::uint8_t, sizeof(T)>>
{
return { static_cast<std::uint8_t>((x >> (indexes * 8)) & 0xffu) ... };
}
template<typename T>
constexpr auto to_le_array(T x) ->
std::enable_if_t<std::is_integral_v<T>,
std::array<std::uint8_t, sizeof(T)>>
{
return to_le_array_helper<T>(x, std::make_index_sequence<sizeof(T)>{});
}
请注意,它在任何平台上都强制使用小端。
这里是godbolt.
如您所见,编译器能够将他的代码优化为:
mov eax, dword ptr [rsp + 8]
mov dword ptr [rsp + 12], eax
注册表值只是复制到存储数组的内存中。
免责声明:
上面的例子假设 8 位字节。这并不一定要总是满足。
是否可以保证对兄弟成员的引用将被优化掉?
Motivation:
struct using_union
{
union
{
std::uint32_t i;
std::uint8_t by[ 4 ];
} data;
};
struct using_alias
{
// shorter notation, no need for .data.i or .data.by
std::uint32_t i;
std::uint8_t (&by)[4] = reinterpret_cast< std::uint8_t(&)[4] >( i );
};
更一般地说,什么时候可以保证优化掉引用?是不是引用的生命周期比引用的生命周期短?
Is there any guarantee that references to sibling members will be optimized out?
不,没有这样的保证。
事实上,虽然可能无法以某种方式明确保证,但您几乎可以指望参考在实践中不会被优化。例如,如果创建一个引用不引用成员的实例,则此类优化将被破坏:
int another = 42;
using_alias an_example {
.i = 1337,
.by = another,
};
没有这样的保证。
注意严格的别名,std::uint8_t[4]
不是 reinterpret_cast
允许的别名类型之一。
此外,根本没有任何变通办法,因为如果您省略联合的名称,其成员将移至外部命名空间,即以下工作:
#include <cstdint>
struct using_union
{
union
{
std::uint32_t i;
std::uint8_t by[ 4 ];
};
};
int main(){
using_union a;
a.i=10;
}
但这仍然不允许您访问标准 C++ 中联合的任何非活动成员。
严格别名
reinterpret_cast
( cppreference.com)
有很多规则。随意查看 cppref,因为它比标准更友好。
相关部分
A glvalue of type T1, designating an object x, can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result is that of *reinterpret_cast<T2 *>(p) where p is a pointer to x of type “pointer to T1”. No temporary is created, no copy is made, and no constructors ([class.ctor]) or conversion functions ([class.conv]) are called. [expr.reinterpret.cast][7.6.1.9.11]
An object pointer can be explicitly converted to an object pointer of a different type.64 When a prvalue v of object pointer type is converted to the object pointer type “pointer to cv T”, the result is static_cast<cv T*>(static_cast<cv void*>(v)). [ Note: Converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value. — end note ] [expr.reinterpret.cast][7.6.1.9.7]
If a program attempts to access ([defns.access]) the stored value of an object through a glvalue whose type is not similar ([conv.qual]) to one of the following types the behavior is undefined
- (11.1) the dynamic type of the object,
- (11.2) a type that is the signed or unsigned type corresponding to the dynamic type of the object, or
- (11.3) a char, unsigned char, or std::byte type.
类型不相似,std::uint8_t[4]
不保证是以下任何一种类型 -> UB。我不认为 std::uint8_t(*)[4]
会衰减到 std::uint8_t*
,但我不是 100% 确定。无论哪种方式,它肯定是 UB,因为即使它确实衰减,std::uint8_t
也不同于 unsigned char
(但实际上它几乎总是如此)。
在 C++ 中处理这个问题的正确方法是 memcpy
联合类型双关语
查看 these 非常好的答案。
TLDR:在 C++ 中访问非活动成员是 UB,在 C 中明确允许。
如果你想检查值 reinterpret_cast<std::byte*>(&value)
是安全的。使用 memcpy
复制 to/from std::byte buffer[]
用于在两个不兼容的类型之间进行转换。不要担心性能,编译器可以发现这些模式。
看起来您正在尝试从 std::uint32_t
转换为 std::uint8_t
的某个数组,这将以小尾数法表示该值(因为这是在 x86 上使用的)。
问题是您的代码会根据平台字节顺序生成不同的结果。这就是为什么这段代码属于 C++ 未定义行为之一(两个版本)。
要在没有 UB 的情况下解决这个问题,您需要不同的代码,然后检查编译器如何处理它。 可能的 C++14 解决方案可能如下所示:
template<typename T, size_t ...indexes>
constexpr auto to_le_array_helper(T x, std::integer_sequence<size_t, indexes...>) ->
std::enable_if_t<std::is_integral_v<T>,
std::array<std::uint8_t, sizeof(T)>>
{
return { static_cast<std::uint8_t>((x >> (indexes * 8)) & 0xffu) ... };
}
template<typename T>
constexpr auto to_le_array(T x) ->
std::enable_if_t<std::is_integral_v<T>,
std::array<std::uint8_t, sizeof(T)>>
{
return to_le_array_helper<T>(x, std::make_index_sequence<sizeof(T)>{});
}
请注意,它在任何平台上都强制使用小端。
这里是godbolt.
如您所见,编译器能够将他的代码优化为:
mov eax, dword ptr [rsp + 8]
mov dword ptr [rsp + 12], eax
注册表值只是复制到存储数组的内存中。
免责声明:
上面的例子假设 8 位字节。这并不一定要总是满足。