繁体   English   中英

C/C++ 为什么要对二进制数据使用 unsigned char?

[英]C/C++ Why to use unsigned char for binary data?

是否真的有必要像某些处理字符编码或二进制缓冲区的库那样使用unsigned char来保存二进制数据? 要理解我的问题,请查看下面的代码 -

char c[5], d[5];
c[0] = 0xF0;
c[1] = 0xA4;
c[2] = 0xAD;
c[3] = 0xA2;
c[4] = '\0';

printf("%s\n", c);
memcpy(d, c, 5);
printf("%s\n", d);

printf's输出正确地,其中f0 a4 ad a2是 Unicode 代码点U+24B62 ()的十六进制编码。

甚至memcpy也正确地复制了 char 保存的位。

什么推理可能提倡使用unsigned char而不是plain char

在其他相关问题中, unsigned char被突出显示,因为它是唯一(字节/最小)数据类型,C 规范保证没有填充。 但正如上面的示例所示,输出似乎不受任何填充本身的影响。

我已经使用 VC++ Express 2010 和 MinGW 编译了上面的内容。 虽然VC给出了警告

warning C4309: '=': truncation of constant value

输出似乎没有反映出这一点。

PS 这可能被标记为Should a buffer of bytes be signed or unsigned char buffer? 的可能重复项? 但我的意图不同。 我在问为什么应该输入unsigned char似乎与char一起工作的东西?

更新:引用 N3337,

Section 3.9 Types

2 对于普通可复制类型 T 的任何对象(基类子对象除外),无论该对象是否持有 T 类型的有效值,构成该对象的基础字节 (1.7) 都可以复制到 char 数组中或无符号字符。 如果将 char 或 unsigned char 数组的内容复制回对象,则对象随后应保持其原始值。

鉴于上述事实以及我的原始示例是在char默认为signed char的 Intel 机器上,我仍然不相信unsigned char是否应该比char更受青睐。

还要别的吗?

在 C 中, unsigned char数据类型是唯一同时具有以下所有三个属性的数据类型

  • 它没有填充位,所有存储位都对数据值有贡献
  • 从该类型的值开始的按位操作在转换回该类型时不会产生溢出、陷阱表示或未定义的行为
  • 它可以在不违反“别名规则”的情况下为其他数据类型起别名,即通过类型不同的指针访问相同数据将保证看到所有修改

如果这些是您正在寻找的“二进制”数据类型的属性,那么您绝对应该使用unsigned char

对于第二个属性,我们需要一个unsigned类型。 对于这些,所有转换都是用模算术定义的,这里是模UCHAR_MAX+1 ,在大多数 99% 的架构中都是256 因此,将更宽的值转换为unsigned char仅对应于截断为最低有效字节。

其他两种字符类型通常不一样。 signed char是有符号的,无论如何,所以不适合它的值的转换没有明确定义。 char不固定为已签名或未签名,但在您的代码移植到的特定平台上,它可能已签名,即使它在您的平台上未签名。

比较单个字节的内容时,您会遇到大部分问题:

char c[5];
c[0] = 0xff;
/*blah blah*/
if (c[0] == 0xff)
{
    printf("good\n");
}
else
{
    printf("bad\n");
}

可以打印“bad”,因为根据您的编译器,c[0] 将被符号扩展为 -1,这与 0xff 完全不同

普通的char类型是有问题的,不应该用于字符串以外的任何东西。 char的主要问题是您无法知道它是有符号的还是无符号的:这是实现定义的行为。 这使得char不同于int等, int总是保证被签名。

虽然VC给出了warning... truncation of constant value

它告诉您您正在尝试将 int 文字存储在 char 变量中。 这可能与符号有关:如果您尝试将值 > 0x7F 的整数存储在有符号字符中,则可能会发生意想不到的事情。 形式上,这是 C 语言中的未定义行为,但实际上,如果尝试将结果打印为存储在(带符号的)char 中的整数值,您只会得到一个奇怪的输出。

在这种特定情况下,警告应该无关紧要。

编辑:

在其他相关问题中,unsigned char 被突出显示,因为它是唯一(字节/最小)数据类型,C 规范保证没有填充。

理论上,根据 C11 6.2.6.2,除 unsigned char 和 signed char 之外的所有整数类型都允许包含“填充位”:

“对于 unsigned char 以外的无符号整数类型,对象表示的位应分为两组:值位和填充位(后者不需要任何一个)。”

“对于有符号整数类型,对象表示的位应分为三组:值位、填充位和符号位。不需要任何填充位;signed char 不应有任何填充位。”

C 标准故意含糊不清,允许这些理论上的填充位,因为:

  • 它允许使用不同于标准 8 位符号表的符号表。
  • 它允许实现定义的符号和奇怪的符号整数格式,例如补码或“符号和大小”。
  • 整数不一定使用分配的所有位。

然而,在 C 标准之外的现实世界中,以下内容适用:

  • 符号表几乎肯定是 8 位(UTF8 或 ASCII)。 存在一些奇怪的例外,但干净的实现在实现大于 8 位的符号表时使用标准类型wchar_t
  • 符号始终是二进制补码。
  • 整数总是使用分配的所有位。

因此,没有真正的理由使用 unsigned char 或 signed char 只是为了躲避 C 标准中的某些理论场景。

字节通常是无符号的 8 位宽整数。

现在, char 不指定整数的符号:在某些编译器上 char 可能是有符号的,在其他编译器上它可能是无符号的。

如果我在您编写的代码中添加位移位操作,那么我将出现未定义的行为。 添加的比较也会有意想不到的结果。

char c[5], d[5];
c[0] = 0xF0;
c[1] = 0xA4;
c[2] = 0xAD;
c[3] = 0xA2;
c[4] = '\0';
c[0] >>= 1; // If char is signed, will the 7th bit go to 0 or stay the same?

bool isBiggerThan0 = c[0] > 0; // FALSE if char is signed!

printf("%s\n", c);
memcpy(d, c, 5);
printf("%s\n", d);

关于编译期间的警告:如果 char 是有符号的,那么您正在尝试分配值 0xf0,它不能在有符号的 char 中表示(范围 -128 到 +127),因此它将被转换为有符号的值(- 16).

将 char 声明为 unsigned 将删除警告,并且在没有任何警告的情况下进行干净的构建总是好的。

普通char类型的符号是实现定义的,因此除非您实际处理字符数据(使用平台字符集的字符串 - 通常是 ASCII),否则通常最好通过使用signed char显式指定符号signed charunsigned char

对于二进制数据,最好的选择很可能是unsigned char ,特别是如果将对数据执行按位运算(特别是位移,它对有符号类型和无符号类型的行为不同)。

我在问为什么应该输入 unsigned char?

如果你做的事情不是标准意义上的“正确”,你就会依赖未定义的行为。 你的编译器今天可能会按照你想要的方式去做,但你不知道明天它会做什么。 您不知道 GCC 或 VC++ 2012 的作用。或者即使行为取决于外部因素或调试/发布编译等。一旦离开标准的安全路径,您可能会遇到麻烦。

好吧,你怎么称呼“二进制数据”? 这是一堆位,没有被称为“二进制数据”的软件的特定部分赋予它们任何意义。 最接近的原始数据类型是什么,它传达了这些位中的任何一个都没有任何特定含义的想法? 我认为unsigned char

是否真的有必要像某些处理字符编码或二进制缓冲区的库那样使用 unsigned char 来保存二进制数据?

“真的”有必要吗? 不。

这是一个非常好的主意,并且有很多原因。

您的示例使用 printf,它不是类型安全的。 也就是说,printf 从格式字符串而不是数据类型中获取格式化提示。 你可以很容易地尝试:

printf("%s\n", (void*)c);

......结果会是一样的。 如果你用 c++ iostreams 尝试同样的事情,结果会不同(取决于 c 的签名)。

什么推理可能提倡使用 unsigned char 而不是普通的 char?

Signed 指定数据的最高有效位(对于 unsigned char 为第 8 位)表示符号。 由于您显然不需要它,因此您应该指定您的数据是无符号的(“符号”位代表数据,而不是其他位的符号)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM