以相同方式对多字节本机类型进行字节操作
byte-wise operation on multibyte native types idomatically
在 C 中,我会毫不犹豫地编写以下内容:
uint32_t value = 0xDEADBEEF;
uint8_t *pValue = &value;
for (size_t i = 0; i < sizeof(value); i++)
{
pValue[i] ^= 0xAA;
}
但在 C++17 中,我的代码扫描器面临两个限制
- 使用“std::byte”进行面向字节的内存访问。
- 用更安全的转换替换“reinterpret_cast”。
后者在我的 C 蜥蜴大脑不可避免地试图强制类型转换时生效。那么,我究竟如何以惯用的方式完成我想做的事情呢?在我的谷歌搜索中,我 运行 通过 memcpy 进出向量的数据,但这感觉很愚蠢。
reinterpret_cast
直观地声明您正在将指针类型转换为(别名安全的)不相关的指针类型,并强制要求没有运行时开销。 (相比之下,C 转换在代码中很难被发现,有些会产生运行时成本)
std::byte
替换 char
及其别名,以表明您 不是 使用可打印字符或数学意义上的“整数” .它是一个任意的位集合 byte.
uint32_t value = 0xDEADBEEF;
std::byte *pValue = reinterpret_cast<std::byte*>(&value);
for (std::size_t i = 0; i < sizeof(value); i++)
{
pValue[i] ^= std::byte{0xAA};
}
如 https://en.cppreference.com/w/cpp/language/
所述
Whenever an attempt is made to read or modify the stored value of an object of type DynamicType through a glvalue of type AliasedType, the behavior is undefined unless one of the following is true: ... AliasedType is std::byte (since C++17), char, or unsigned char: this permits examination of the object representation of any object as an array of bytes.
所以,reinterpret_cast
是一个 eldritch abomination,但无论你给它什么类型的指针都不会给你任何错误,除了 这 3允许命名字节的方式。
因此,“更安全”的转换将是一个函数,专门只为您提供任意对象的字节数组视图,而不是公开 reinterpret_cast
并让审阅者检查所使用的类型是允许的,并且以惯用的方式使用。
与此同时,您需要编写一个老式的计数 for
循环 和 索引指针以获取每个元素。您也可以让“更安全”(更具体、更有针对性)功能来解决这个问题,方法是提供一个字节范围适当的结果,而不仅仅是指向开头的指针。
最简单的方法是使用std::span
。
就在我的脑海里:
template <typename T>
auto raw_byte_view (T& original)
{
constexpr auto sz = sizeof(T);
auto& arr = reinterpret_cast<std::byte(&)[sz]>(&original);
return std::span<std::byte,sz>(arr);
}
我没试过,但似乎要获得 span
的编译时大小版本,您必须提供数组引用,而不是单独的指针、长度参数。您可以将 reference 转换为数组,它包括大小作为类型的一部分。
讨论
返回 span
作为数组引用的包装器比尝试处理数组引用本身更好,因为很容易将数组 reference 错误处理为它必须作为参考。您可以使用指向 std::array
的指针来完成同样的事情,这与 C 数组不同,它像任何普通类型一样工作。但我们需要引用语义——返回的东西是对现有对象的引用,而不是要复制的东西。因此,最好使用表示这种间接的类型,而不是在基于值的类型之上堆叠另一个指针。
结束讨论
现在你可以写:
for (auto& x : raw_byte_view(value)) {
x ^= std::byte{0xAA};
}
在 C 中,我会毫不犹豫地编写以下内容:
uint32_t value = 0xDEADBEEF;
uint8_t *pValue = &value;
for (size_t i = 0; i < sizeof(value); i++)
{
pValue[i] ^= 0xAA;
}
但在 C++17 中,我的代码扫描器面临两个限制
- 使用“std::byte”进行面向字节的内存访问。
- 用更安全的转换替换“reinterpret_cast”。
后者在我的 C 蜥蜴大脑不可避免地试图强制类型转换时生效。那么,我究竟如何以惯用的方式完成我想做的事情呢?在我的谷歌搜索中,我 运行 通过 memcpy 进出向量的数据,但这感觉很愚蠢。
reinterpret_cast
直观地声明您正在将指针类型转换为(别名安全的)不相关的指针类型,并强制要求没有运行时开销。 (相比之下,C 转换在代码中很难被发现,有些会产生运行时成本)
std::byte
替换 char
及其别名,以表明您 不是 使用可打印字符或数学意义上的“整数” .它是一个任意的位集合 byte.
uint32_t value = 0xDEADBEEF;
std::byte *pValue = reinterpret_cast<std::byte*>(&value);
for (std::size_t i = 0; i < sizeof(value); i++)
{
pValue[i] ^= std::byte{0xAA};
}
如 https://en.cppreference.com/w/cpp/language/
所述Whenever an attempt is made to read or modify the stored value of an object of type DynamicType through a glvalue of type AliasedType, the behavior is undefined unless one of the following is true: ... AliasedType is std::byte (since C++17), char, or unsigned char: this permits examination of the object representation of any object as an array of bytes.
所以,reinterpret_cast
是一个 eldritch abomination,但无论你给它什么类型的指针都不会给你任何错误,除了 这 3允许命名字节的方式。
因此,“更安全”的转换将是一个函数,专门只为您提供任意对象的字节数组视图,而不是公开 reinterpret_cast
并让审阅者检查所使用的类型是允许的,并且以惯用的方式使用。
与此同时,您需要编写一个老式的计数 for
循环 和 索引指针以获取每个元素。您也可以让“更安全”(更具体、更有针对性)功能来解决这个问题,方法是提供一个字节范围适当的结果,而不仅仅是指向开头的指针。
最简单的方法是使用std::span
。
就在我的脑海里:
template <typename T>
auto raw_byte_view (T& original)
{
constexpr auto sz = sizeof(T);
auto& arr = reinterpret_cast<std::byte(&)[sz]>(&original);
return std::span<std::byte,sz>(arr);
}
我没试过,但似乎要获得 span
的编译时大小版本,您必须提供数组引用,而不是单独的指针、长度参数。您可以将 reference 转换为数组,它包括大小作为类型的一部分。
讨论
返回 span
作为数组引用的包装器比尝试处理数组引用本身更好,因为很容易将数组 reference 错误处理为它必须作为参考。您可以使用指向 std::array
的指针来完成同样的事情,这与 C 数组不同,它像任何普通类型一样工作。但我们需要引用语义——返回的东西是对现有对象的引用,而不是要复制的东西。因此,最好使用表示这种间接的类型,而不是在基于值的类型之上堆叠另一个指针。
结束讨论
现在你可以写:
for (auto& x : raw_byte_view(value)) {
x ^= std::byte{0xAA};
}