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

Lyb*_*rta 58 c++ strict-aliasing language-lawyer c++11 uint8t

现在我们有时必须使用二进制数据.在C++中,我们使用字节序列,因为开头char是我们的构建块.定义为sizeof1,它是字节.char默认情况下,所有库I/O函数都使用.一切都很好,但总是有一点担心,有点奇怪,一些人的错误 - 一个字节中的位数是实现定义的.

所以在C99中,决定引入几个typedef让开发人员轻松表达自己的固定宽度整数类型.当然可选,因为我们从不想伤害便携性.其中uint8_t,迁移到C++ 11中std::uint8_t,固定宽度的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二进制数据.或许,我们中的一些人不关心字节中的位数并且对它感到满意.

但遗憾的是,两个世界相互冲突,有时您必须将数据作为char*并将其传递给期望的库std::uint8_t*.但是等等,你说,是不是char变量位并std::uint8_t固定为8?它会导致数据丢失吗?

嗯,这里有一个有趣的标准.的char定义为保持正好一个字节和字节是内存的最低可寻址的块,所以用比特宽度比的较小不能有一个类型char.接下来,它被定义为能够保存UTF-8代码单元.这给了我们最小--8位.所以现在我们有一个typedef,它要求是8位宽,并且是一个至少8位宽的类型.但有其他选择吗?是的,unsigned char.请记住,签名char是实现定义的.还有其他任何一种 谢天谢地,没有.所有其他整数类型都需要超出8位的范围.

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

一旦我们有这方面的知识,我们已经8位字节,这std::uint8_t是实现为char或者unsigned char,我们可以假设,我们可以做reinterpret_castchar*std::uint8_t*,反之亦然?它是便携式的吗?

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

std::uint8_t* buffer = /* ... */ ;
char* buffer2 = reinterpret_cast<char*>(buffer);
std::uint8_t buffer3 = reinterpret_cast<std::uint8_t*>(buffer2);
Run Code Online (Sandbox Code Playgroud)

如果我们不接触是安全的buffer2.如我错了请纠正我.

因此,考虑到以下先决条件:

  • CHAR_BIT == 8
  • std::uint8_t 被定义为.

它是便携和安全投char*std::uint8_t*来回,假设我们用二进制数据和潜在的缺乏标志的工作char没有关系?

我将非常感谢对标准的引用和解释.

编辑:谢谢,杰里科芬.我要添加标准引用([basic.lval],§3.10/ 10):

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

...

- char或unsigned char类型.

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

Lyb*_*rta 25

好吧,让我们变得真正迂腐.看完这个,这个这个,我很有信心,我理解这两种标准背后的意图.

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

但是,这样做reinterpret_castchar*std::uint8_t*,然后解引用所得指针是违反严格别名规则并且未定义的行为,如果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.");
Run Code Online (Sandbox Code Playgroud)

使用此断言,您的代码将无法在将导致未定义行为的平台上进行编译.

第二:

std::memcpy(uint8buffer, charbuffer, size);
Run Code Online (Sandbox Code Playgroud)

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

要重申的是,为了能够reinterpret_cast之间char*std::uint8_t*工作,导致指针可移植性安全地在100%符合标准的方式,以下条件必须为真:

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

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

  • 最好向标准化委员会提出什么问题.我将欣赏一个单独的整数类型,其保证大小为1个字节,并且没有字符语义. (3认同)
  • `std::uint8_t` 不必是 `unsigned char` 才能移植:它可能是 `char`,如果 `char` 在给定的实现中被定义为无符号。(不要忘记,`char` 有 3 种类型,而不是其他整数类型的 2 种。) (2认同)

Jer*_*fin 20

如果uint8_t存在,基本上唯一的选择是它是一个typedef unsigned char(或者char它恰好是无符号的).没有(但是一个位域)可以表示比a更少的存储char,并且唯一可以小到8位的其他类型是a bool.下一个最小的正常整数类型是a short,必须至少为16位.

因此,如果uint8_t在所有存在,你真的只有两种可能:你要么铸造unsigned charunsigned char,或铸造signed charunsigned char.

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

由于这包括charunsigned char,并且作为一系列char访问它的强制转换也给出了定义的行为.

编辑:就Luc提到的扩展整数类型而言,我不确定你是如何设法应用它来改变这种情况的.C++引用了C99等定义的标准uint8_t,因此其余部分的引号来自C99.

§6.2.6.1/ 3规定unsigned char应使用纯二进制表示,不带填充位.填充位仅在6.2.6.2/1中允许,具体排除unsigned char.然而,该部分详细描述了纯二进制表示 - 字面意思是位.因此,unsigned charuint8_t(如果存在)必须在位级别上相同地表示.

为了看到两者之间的差异,我们必须断言,当被视为一个特定位时,某些特定位将产生与另一个视图不同时的结果 - 尽管两者必须在位级别具有相同的表示.

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

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

  • 如果一个实现提供了一个(例如)`__u8`扩展整数类型,并将其用于`uint8_t`,即使它与`unsigned char`具有完全相同的表示,访问任何对象的特殊异常为`unsigned char`不延伸到`__u8`.实现可能会提供***,以便优化器可以通过假设没有别名来做得更好. (8认同)
  • 你说:"如果uint8_t存在[...]它是[...]的typedef(或者如果碰巧是无符号则是char)"我认为这是不正确的.`uint8_t`必须是无符号整数类型的typedef,`char`不是这种类型,无论它是否有符号.类似地,`char`不是有符号整数类型.我在[这里](http://stackoverflow.com/a/16006093/1137388)和[这里](http://stackoverflow.com/a/16002781/1137388)之前就此进行过辩论(请阅读其中的评论) ).`uint8_t`可以(并且可能会但不是必需的)用于`unsigned char`的typedef. (3认同)
  • @JerryCoffin:我的立场得到了纠正.您的参考文章诀窍:C++标准不要求该大小,但它引用了§3.9.1.3中C99标准的§5.2.4.2.1,并强制要求满足这些要求. (2认同)