使用 char 或 unsigned char 数组存储原始数据更好吗?

Posted

技术标签:

【中文标题】使用 char 或 unsigned char 数组存储原始数据更好吗?【英文标题】:Is it better to use char or unsigned char array for storing raw data? 【发布时间】:2014-08-02 14:06:25 【问题描述】:

当需要在内存中缓冲一些原始数据时,例如来自流的数据,使用charunsigned char 的数组更好吗?我一直使用char,但在工作中说它更好unsigned char,我不知道为什么。

【问题讨论】:

如果是字符串流,那么使用char数组就可以了。对于其他数字(例如,十六进制,位)原始数据,最好使用unsigned变量,这样就不必处理符号位 【参考方案1】:

在内部,完全一样:每个元素都是一个字节。当您使用这些值进行操作时会给出差异。

如果您的值范围是 [0,255],则应使用 unsigned char,但如果是 [-128,127],则应使用 signed char

假设您使用第一个范围(signed char),那么您可以执行操作100+100。否则该操作将溢出并给您一个意外的值。

根据您的编译器或机器类型,char 默认可能是无符号或有符号的: Is char signed or unsigned by default? 因此,char 具有上述情况所描述的范围。

如果你只是使用这个缓冲区来存储二进制数据而不使用它,那么使用charunsigned char没有区别。

编辑

请注意,您甚至可以使用编译器的标志 change the default char 为同一台机器和编译器:

-funsigned-char 让 char 类型为 unsigned,如 unsigned char。

每一种机器都有一个默认的 char 应该是什么。默认情况下,它要么类似于 unsigned char,要么默认情况下类似于 signed char。 理想情况下,可移植程序在依赖于对象的签名时应始终使用有符号字符或无符号字符。但是很多 已编写程序以使用普通字符并期望它是 已签名或期望未签名,具体取决于他们的机器 为。这个选项,以及它的反面,让你做出这样的 程序使用相反的默认值。

char 类型始终是与每个有符号字符或无符号字符不同的类型,即使它的行为总是与 那两个。

【讨论】:

您假设 char 已签名。所以“范围”和“溢出”部分不一定是真的。 "如果是 [-127,127] 使用char。" char 也可能未签名,如果您需要签名,请使用 signed char。 “……给你一个负数。”也许,也许不是,签名溢出是 UB。 @BaummitAugen 确实如此,但在这种情况下,OP 不应期望获得所需的值。【参考方案2】:

通常使用char 会更好,但它的区别很小,没关系。它是原始数据,因此您应该简单地传递它,而不是尝试通过char 一种或另一种类型的指针来使用它。由于char 是本机数据类型,因此使用它最有意义,而不是想象您将数据强制转换为一种或另一种类型。

【讨论】:

【参考方案3】:

就缓冲区的结构而言,没有区别:在这两种情况下,您都会得到一个字节的元素大小,这是标准规定的。

也许您获得的最重要的区别是您在访问缓冲区的各个元素时看到的行为,例如,用于打印。使用char,您可以获得实现定义的有符号或无符号行为;使用unsigned char,您总是会看到未签名的行为。如果您想打印“原始数据”缓冲区的各个字节,这一点就变得很重要。

另一个用于缓冲区的好选择是精确宽度整数uint8_t。它保证与unsigned char 具有相同的宽度,它的名称需要更少的输入,并且它告诉读者您不打算将缓冲区的各个元素用作基于字符的信息。

【讨论】:

【参考方案4】:

更新:C++17 引入了std::byte,它比使用任何形式的char 更适合“原始”数据缓冲区。

对于早期的 C++ 版本:

unsigned char 强调数据不是“只是”文本

如果您从例如压缩流、数据库表备份文件、可执行映像、jpeg...然后unsigned 适合上面提到的二进制数据内涵

unsigned 更适合您可能想要对二进制数据执行的某些操作,例如有符号类型的某些位操作存在未定义和实现定义的行为,unsigned 值可以直接用作数组中的索引

您不能不小心将 unsigned char* 传递给期望 char* 的函数,并让它作为假定文本运行

在这些情况下,通常更自然地认为值在 0..255 范围内,毕竟 - 为什么“符号”位与数据?

如果您要存储“原始数据”——在应用程序逻辑/设计级别恰好是 8 位数字数据,那么请务必选择unsigned明确 signed char 适合您的需求

【讨论】:

【参考方案5】:

如果您使用 unsigned char,那么它将只使用有效的 ASCII 字符,因为它的范围将变为 -127 到 +127。

您可以在这个问题中找到 char 和 unsigned char 详细信息之间的完全区别。

diff bet char and unsigned char

你可以在这里看到表格。

ASCII table

complete tables of raw characters

【讨论】:

【参考方案6】:

正如@Pablo 在他的回答中所说,关键原因是如果你对字节进行算术运算,如果你将字节声明为unsigned char,你会得到“正确”的答案:你想要(在 Pablo 的示例)100 + 100 添加到 200;如果你用signed char 做那个总和(如果你的编译器上的char 是签名的,你可能会意外地这样做)没有保证 - 你在自找麻烦。

另一个重要原因是,如果您明确说明什么是数据类型,它可以帮助记录您的代码。声明很有用

typedef unsigned char byte

甚至更好

#include <stdint.h>
typedef uint8_t byte

此后使用byte 可以更清楚地说明您的程序的意图是什么。根据您的编译器的偏执程度(-Wall 是您的朋友),如果您将 byte* 参数提供给 char* 函数参数,这个可能甚至会导致类型警告,从而提示您稍微仔细考虑一下您是否在做正确的事情。

“字符”从根本上与“字节”完全不同。 C 恰好模糊了区别(因为在 C 的级别上,在大多数 ASCII 世界中,区别在许多情况下并不重要)。这种模糊并不总是有帮助,但它至少是一种良好的智力卫生,可以让你头脑中的差异保持清晰。

【讨论】:

【参考方案7】:

如果您能够使用 C++17,则有一种更适合处理原始数据的 std::byte 类型。它只定义了按位逻辑运算符。

【讨论】:

以上是关于使用 char 或 unsigned char 数组存储原始数据更好吗?的主要内容,如果未能解决你的问题,请参考以下文章

什么是unsigned char;;?

Gcc:强制编译器默认使用unsigned char

ARDUINO 中将一个unsigned char 的数组的数据转化为一个long 型的数

将 unsigned char * 复制到 unsigned char*

Qt笔记-char[]或unsigned char[]转QByteArray时要注意的地方

Qt笔记-char[]或unsigned char[]转QByteArray时要注意的地方