以相同方式对多字节本机类型进行字节操作

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 中,我的代码扫描器面临两个限制

后者在我的 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};
}