字符* 和 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
迁移到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
,我们是否可以假设我们可以从 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(中的引用:
如果程序尝试通过 glvalue 而不是 以下类型的行为未定义:
。
— 字符或无符号字符类型。
编辑2:好的,更深入。 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);
Cppreference表示,std::memcpy
以unsigned char
数组的形式访问对象,因此它是安全和可移植的。
重申一下,为了能够在char*
和std::uint8_t*
之间reinterpret_cast
并以 100% 符合标准的方式可移植且安全地处理生成的指针,必须满足以下条件:
-
CHAR_BIT == 8
. -
std::uint8_t
已定义。 -
std::uint8_t
按char
或unsigned char
实现。
实际上,上述条件在 99% 的平台上都是正确的,并且可能没有平台的前 2 个条件为真,而第三个条件为假。
如果uint8_t
存在,基本上唯一的选择是它是unsigned char
的typedef(如果它碰巧是无符号的,则char
(。没有什么(除了位域(可以表示比char
更少的存储,而唯一可以小至8位的另一种类型是bool
。下一个最小的普通整数类型是short
,它必须至少为16位。
因此,如果uint8_t
存在,你实际上只有两种可能性:要么将unsigned char
投射到unsigned char
,要么将signed char
投射到unsigned char
。
前者是身份转换,所以显然是安全的。后者属于 §3.10/10 中作为 char 或无符号 char 序列访问任何其他类型的"特殊分配",因此它也给出了定义的行为。
由于这包括char
和unsigned 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
(如果存在(必须在位级别以相同的方式表示。
为了看到两者之间的差异,我们必须断言,当被视为一个时,某些特定位会产生与被视为另一个时不同的结果 - 尽管两者在位级别上必须具有相同的表示。
更直接地说:两者之间结果的差异要求它们以不同的方式解释位 - 尽管直接要求它们以相同的方式解释位。
即使在纯粹的理论层面上,这似乎也很难实现。在任何接近实际水平的东西上,这显然是荒谬的。
- 如何理解C++标准N3337中的expr.const.cast子句8
- C++Cast运算符过载
- 错误:"cast"未命名类型void setCastDescription(std::string
- 通过使用 const-cast 的非常量引用来延长临时的寿命
- "(void) cast"与功能有什么区别 "__attributes__"来沉默未使用的参数警告?
- C++:"Expected '(' for function-style cast or type construction"错误
- 为什么选择 g++ 给予者:"error: cast to pointer from integer of different size [-Werror=int-to-pointer-cast]"
- Gtk+ g_signal_connect() 和 C++ lambda 会导致"invalid cast"错误
- Shared_ptr cast vs static_cast speed
- C++20 中的严格别名规则是否允许标准 c++ unicode 字符和下划线类型之间"reinterpret
- 在 iOS 上使用 Aruco 构建 OpenCV 时"Functional-style cast from id to double is not allowed"
- 覆盖 CAST 运算符(我认为它被称为向下转换)
- C++错误,隐 <function-style-cast> 式要求使用模板化类一次调用多个构造函数的多个转换
- 如何修复<function-style-cast>错误:无法从'initializer list'转换为asdending比较<W>(模板函子)
- C++ C++ 中的函数声明,键入 CAST
- static_cast会丢弃错误,但C风格的演员cast有效
- C++ cast char * to unsigned char
- 禁用 Clang 中的"cast from pointer to smaller type uint32_t"错误
- 是否可以使用gcc 3.3版修复与int*cast相关的Sun Solaris OS 5.8分段故障
- C++ const-cast 一个引用