繁体   English   中英

char* 和 std::uint8_t* 之间的 reinterpret_cast - 安全吗?

[英]reinterpret_cast between char* and std::uint8_t* - safe?

现在我们有时都必须处理二进制数据。 在 C++ 中,我们使用字节序列,因为开始的char是我们的构建块。 定义为sizeof为 1,它是字节。 并且所有库 I/O 函数默认使用char 一切都很好,但总有一点点担心,有些奇怪的东西让一些人感到困扰——一个字节中的位数是实现定义的。

所以在 C99 中,决定引入几个 typedef 来让开发人员轻松表达他们自己,固定宽度的整数类型。 当然是可选的,因为我们从不想损害可移植性。 其中, uint8_t ,作为std::uint8_t迁移到 C++11 中,是一种固定宽度的 8 位无符号整数类型,对于真正想要使用 8 位字节的人来说是完美的选择。

因此,开发人员接受了新工具并开始构建库,明确声明他们接受 8 位字节序列,如std::uint8_t*std::vector<std::uint8_t>或其他。

但是,也许是经过深思熟虑,标准化委员会决定不要求实现std::char_traits<std::uint8_t>因此禁止开发人员轻松且可移植地实例化std::basic_fstream<std::uint8_t>和轻松读取std::uint8_t s 作为二进制数据。 或者,我们中的一些人不关心字节中的位数,并且对此感到满意。

但不幸的是,两个世界发生冲突,有时您必须将数据作为char*并将其传递给需要std::uint8_t*的库。 但是等等,你说, char变量位和std::uint8_t不是固定为 8 吗? 会导致数据丢失吗?

嗯,关于这个有一个有趣的 Standardese。 char定义为恰好保存一个字节,而 byte 是内存的最低可寻址块,因此不能存在位宽小于char 接下来,它被定义为能够容纳 UTF-8 代码单元。 这给了我们最小的 - 8 位。 所以现在我们有一个 typedef,它需要 8 位宽,类型至少为 8 位宽。 但是有其他选择吗? 是的, unsigned char 请记住, char符号是实现定义的。 还有其他类型吗? 谢天谢地,没有。 所有其他整数类型都需要超出 8 位的范围。

最后, std::uint8_t是可选的,这意味着如果未定义,则使用此类型的库将无法编译。 但如果它编译呢? 我可以非常自信地说,这意味着我们在一个具有 8 位字节和CHAR_BIT == 8

一旦我们知道我们有 8 位字节, std::uint8_t被实现为charunsigned char ,我们是否可以假设我们可以从char*std::uint8_t*进行reinterpret_cast ,反之亦然? 便携吗?

这就是我的标准阅读技巧让我失望的地方。 我阅读了安全派生指针( [basic.stc.dynamic.safety] ),据我所知,以下内容:

std::uint8_t* buffer = /* ... */ ;
char* buffer2 = reinterpret_cast<char*>(buffer);
std::uint8_t buffer3 = reinterpret_cast<std::uint8_t*>(buffer2);

如果我们不触摸buffer2是安全的。 如果我错了纠正我。

因此,鉴于以下先决条件:

  • CHAR_BIT == 8
  • std::uint8_t已定义。

假设我们正在处理二进制数据并且可能缺少char的符号并不重要,那么来回转换char*std::uint8_t*是否可移植且安全?

我将不胜感激对标准的引用和解释。

编辑:谢谢,杰瑞棺材。 我将添加来自标准的引用([basic.lval],§3.10/10):

如果程序尝试通过以下类型之一以外的泛左值访问对象的存储值,则行为未定义:

...

— char 或 unsigned char 类型。

EDIT2:好的,更深入。 std::uint8_t不能保证是unsigned char 它可以实现为扩展的无符号整数类型,并且扩展的无符号整数类型不包含在 §3.10/10 中。 现在怎么办?

好吧,让我们真正学究。 阅读完这个这个这个之后,我非常有信心我理解这两个标准背后的意图。

因此,从std::uint8_t*char*进行reinterpret_cast然后取消引用结果指针是安全可移植的,并且[basic.lval]明确允许。

然而,从char*std::uint8_t*进行reinterpret_cast然后解引用结果指针违反了严格的别名规则,如果std::uint8_t被实现为扩展的无符号整数类型,则是未定义的行为

但是,有两种可能的解决方法,首先:

static_assert(std::is_same_v<std::uint8_t, char> ||
    std::is_same_v<std::uint8_t, unsigned char>,
    "This library requires std::uint8_t to be implemented as char or unsigned char.");

有了这个断言,你的代码将不会在平台上编译,否则会导致未定义的行为。

第二:

std::memcpy(uint8buffer, charbuffer, size);

Cppreferencestd::memcpy对象作为unsigned char数组访问,因此它是安全可移植的

重申一下,为了能够在char*std::uint8_t*之间reinterpret_cast并以 100% 符合标准的方式可移植安全地使用结果指针,以下条件必须为真:

  • CHAR_BIT == 8
  • std::uint8_t已定义。
  • std::uint8_t被实现为charunsigned char

实际上,上述条件在 99% 的平台上都为真,并且可能没有平台前两个条件为真而第三个条件为假。

如果uint8_t存在,本质上唯一的选择是它是unsigned char (或char如果碰巧是 unsigned)的 typedef。 没有任何东西(但位域)能比char表示更少的存储空间,唯一可以小到 8 位的其他类型是bool 下一个最小的正常整数类型是short ,它必须至少为 16 位。

因此,如果uint8_t根本存在,那么您实际上只有两种可能性:您要么将unsigned charunsigned char ,要么将signed charunsigned char

前者是身份转换,所以显然是安全的。 后者属于 §3.10/10 中为访问任何其他类型作为 char 或 unsigned char 序列而给出的“特殊豁免”,因此它也给出了定义的行为。

由于这包括charunsigned char ,因此将其作为 char 序列访问的强制转换也给出了定义的行为。

编辑:就 Luc 提到的扩展整数类型而言,我不确定在这种情况下您将如何应用它来获得差异。 对于uint8_t等的定义,C++ 参考了 C99 标准,因此其余部分的引号来自 C99。

§6.2.6.1/3 规定unsigned char应使用纯二进制表示,没有填充位。 填充位仅在 6.2.6.2/1 中允许,其中明确排除了unsigned char 然而,该部分详细描述了纯二进制表示——字面意思。 因此, unsigned charuint8_t (如果存在)必须在位级别表示相同。

要查看两者之间的差异,我们必须断言某些特定位在被视为一个时会产生与被视为另一个时不同的结果——尽管事实上两者在位级别必须具有相同的表示。

更直接地说:两者之间的结果差异要求它们以不同的方式解释位——尽管直接要求它们以相同的方式解释位。

即使在纯粹的理论水平上,这似乎也难以实现。 在任何接近实际水平的事情上,这显然是荒谬的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM