[英]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 标准故意含糊不清,允许这些理论上的填充位,因为:
然而,在 C 标准之外的现实世界中,以下内容适用:
因此,没有真正的理由使用 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 char
或unsigned 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.