字节类型:std::byte vs std::uint8_t vs unsigned char vs char vs std::bitset<8>

4
C++有很多类型模糊地描述了同一件事情。假设我们正在编译一个字节为8位的架构,以下所有类型都有些相似:
- std::byte - std::uint8_t - std::bitset<8> - unsigned char (8位) - char (8位)
如果一个字节是8位,这些类型是否可以互换使用?如果不能,那么什么情况下需要使用其中一种而不是另一种呢?
我经常在Stack Overflow上看到类似将十六进制字符串转换为字节数组的问题,其中有人使用std::uint8_t、char、unsigned char和其他类型来表示一个"字节"。这只是风格偏好的问题吗?
注意:这个问答是一个社区常见问题解答,鼓励编辑。关于何时使用何种类型的“byte”以及原因的问题经常出现,尽管C++17引入了std::byte,看起来选择很明显。有一个解答关于std::bitsetstd::uint8_t等被称为“byte”的所有误解的常见问题解答是很有用的。鼓励编辑。

注意:如果你希望这是一个真正的协作过程,鼓励他人进行编辑,那么最好将其发布为社区维基。这是所有常见问题类型帖子的一个很好的标准。 - undefined
1个回答

8
对于8位架构,所有列出的类型在某种程度上都是模拟具有8位的东西。然而,使用情况基本不同,只有其中一些类型被保证具有特殊属性,使它们可用作字节类型。
概述
类型 定义 目的
std::byte enum class byte : unsigned char {}; 规范的字节类型
✔️ 所有特殊属性
unsigned char 基本类型 字符 / 旧字节类型 / 小算术类型
✔️ 所有特殊属性
signed char 基本类型 字符 / 小算术类型
❌ 没有特殊属性
char 基本类型,与
signed charunsigned char 具有相同的底层类型
字符
⚠️ 仅有部分特殊属性
char8_t 基本类型,底层类型为 unsigned char UTF-8 字符
❌ 没有特殊属性
std::uint8_t typedef unsigned char uint8_t;
(这不是保证,只是最常见的实现方式。)
8 位无符号算术
⚠️ 不保证特殊属性
std::bitset<8> template <std::size_t N>
class bitset;
8 位比特集合;可能比 8 位更宽
❌ 没有特殊属性
请参阅问题末尾的附录,其中列出了所有这些特殊属性,按类型分类。
std::byte(C++17)
这是C++中的规范字节类型。每当你自问“我应该使用哪种类型来表示这些字节?”时,std::byte就是答案。
请注意,std::byte非常特殊,因为有许多放宽规定,允许您以其他未定义的方式使用该类型。例如,对于std::byte,严格别名规则被放宽(参见[basic.lval] p11),这意味着您可以将任何对象视为std::byte数组进行检查。
大多数其他类型都没有这些特殊能力,试图将它们用作字节将导致未定义行为。
适用于原始内存操作的std::byte虽然合适,但许多旧的API(如iostream库)是在其之前设计的,不适用于它。 这种类型也有些笨重(例如,my_byte == 0是不可能的)。 不要试图强行在不适用std::byte的库中使用它。

另请参阅:C++中是否有“byte”数据类型?, std::byte的目的是什么?, P0298 - 字节类型定义*

unsigned char

在C++17之前,这是最接近“byte”的东西。 unsigned char具有std::byte的所有特殊属性。

然而,名称非常令人困惑,有时也被视为字符。例如,std::ostream::operator<<将其打印为ASCII字符,而不是打印其数值。此外,在使用unsigned char进行算术运算时,会将其提升为int,这对于一个“byte”来说似乎不合适。

总的来说,它是一种模糊不清的类型,同时是一个字节、一个字符和一个算术类型。建议使用std::bytecharstd::uint8_tstd::uint_least8_t代替。
参见:如何在需要旧式无符号字符的地方使用新的std::byte类型?

signed char

有符号字符是无符号字符的对应物,同样令人困惑。它几乎没有std::byte和无符号字符的特殊属性,而是一种奇怪的算术和字符类型的混合体。应该避免使用它。
一个更好的选择是std::int_least8_t,它也是有符号的,也保证至少有8位宽度,但没有奇怪的字符含义。

另请参阅:有符号/无符号字符的区别

char

这是一个独立的类型,其底层类型与signed charunsigned char相同。 它具有unsigned charstd::byte的大部分(但不是全部)特性。 例如,与unsigned char不同,它不为在char[]中创建的对象提供存储空间([intro.object] p3)。

char应该用于其名称所指的用途:一个字符


另请参阅:char!=(有符号字符),char!=(无符号字符)

char8_t(C++20)

最初有关此类型具有类似于char的特殊属性的讨论,但最终没有任何特殊属性。 它的底层类型是unsigned char,但与std::byte不同,这并不意味着它继承了任何属性。

它应该用作UTF-8字符,可能在UTF-8编码的字符串中使用。

std::uint8_t(C++11)

这种类型是一个在C中开始的设计错误。 虽然这并不保证,但通常实现为类型别名,如

typedef unsigned char uint8_t;

这意味着它在实践中具有无符号字符的特殊属性(因为所有编译器都是这样实现的),但这些特性在标准中并没有得到保证。 它能够与其他类型进行别名处理的事实也可能对性能产生不利影响,与其作为唯一类型的别名相比。
需要注意的一点是,在C++中,并不能保证一个字节是8位。 许多人使用std::uint8_t,因为它提供了“真正”的8位安全性。 然而,std::uint8_t是可选的,并且在字节宽度大于8位的平台上不存在,因此它的可移植性并不比以下代码更好:
#include <climits>
static_assert(CHAR_BIT == 8); // ... and use unsigned char or char as a byte type

对于更便携的8位算术类型,有std::uint_fast8_tstd::uint_fast8_t,它们保证存在但可能比8位更宽。
请注意,std::uint8_tstd::uint_least8_tstd::uint_fast8_t都可以像unsigned char一样提升为int
参见:uint8_t vs unsigned charWhat platforms have something other than 8-bit char?

std::bitset<8>

这是与"字节"类型最不相似的类型。 它模拟了一系列的位,或者根据视角而定的一组数字。
在大多数实现中,std:bitset<8>至少与int一样大,所以它甚至不是8位大。只有在需要一组位时才使用这种类型。它不是一个字节。
结论 std::byte是唯一模拟字节的类型,没有多余的东西,也没有少了的东西。在可能的情况下,应该优先选择它作为字节类型。 所有其他类型要么缺少关键属性,要么具有与字节完全不同的目的。
附录 std::byte和普通字符类型的特殊属性
部分 受影响的类型 特殊属性
[intro.object] p3 unsigned char[], std::byte[] 数组提供了用于放置对象的存储空间
[intro.object] p13 unsigned char[], std::byte[] 数组在其生命周期开始时隐式创建对象
[basic.life] p6.4 cv char*, cv unsigned char*, 和 cv std::byte* 允许对超出生命周期的对象指针进行static_cast
[basic.indet] unsigned ordinary character types, std::byte 在初始化和赋值时允许不确定的结果
[basic.types.general] p2 char[], unsigned char[], std::byte[] 可以通过数组传递可平凡复制的对象的值
[basic.lval] p11.3 char, unsigned char, std::byte 宽松的严格别名
[expr.new] p16 char[], unsigned char[], std::byte[] new-expression中的更严格的对齐
[bit.cast] p2 unsigned ordinary character types, std::byte std::bit_cast允许不确定的结果
注意:目前还不清楚“无符号普通字符类型”实际上是什么意思。请参阅编辑问题 5070

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接