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_cast的char*到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 == 8std::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_cast从char*到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实现为char或unsigned char.实际上,上述条件在99%的平台上都是正确的,并且可能没有前两个条件为真的平台,而第三个条件为假.
Jer*_*fin 20
如果uint8_t存在,基本上唯一的选择是它是一个typedef unsigned char(或者char它恰好是无符号的).没有(但是一个位域)可以表示比a更少的存储char,并且唯一可以小到8位的其他类型是a bool.下一个最小的正常整数类型是a short,必须至少为16位.
因此,如果uint8_t在所有存在,你真的只有两种可能:你要么铸造unsigned char到unsigned char,或铸造signed char到unsigned char.
前者是身份转换,所以显然是安全的.后者属于在§3.10/ 10中为访问任何其他类型作为char或unsigned char序列而给出的"特殊分配",因此它也给出了定义的行为.
由于这包括char和unsigned char,并且作为一系列char访问它的强制转换也给出了定义的行为.
编辑:就Luc提到的扩展整数类型而言,我不确定你是如何设法应用它来改变这种情况的.C++引用了C99等定义的标准uint8_t,因此其余部分的引号来自C99.
§6.2.6.1/ 3规定unsigned char应使用纯二进制表示,不带填充位.填充位仅在6.2.6.2/1中允许,具体排除unsigned char.然而,该部分详细描述了纯二进制表示 - 字面意思是位.因此,unsigned char和uint8_t(如果存在)必须在位级别上相同地表示.
为了看到两者之间的差异,我们必须断言,当被视为一个特定位时,某些特定位将产生与另一个视图不同时的结果 - 尽管两者必须在位级别具有相同的表示.
更直接地说:两者之间的结果差异要求它们以不同方式解释位 - 尽管直接要求它们相同地解释位.
即使在纯理论水平上,这似乎也很难实现.在接近实际水平的任何事情上,这显然是荒谬的.