是否真的有必要使用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
输出似乎没有反映出来。
P.S。这可以标记为Should a buffer of bytes be signed or unsigned char buffer?的可能重复,但我的意图是不同的。我在问为什么char
的{{1}}似乎工作正常?{/ 1}}
更新:引用N3337,
unsigned char
2对于任何简单的对象(基类子对象除外) 可复制类型T,无论对象是否包含有效的类型值 T,构成对象的底层字节(1.7)可以复制到 char或unsigned char数组。如果是char数组的内容 或者将unsigned char复制回对象,该对象应该 随后保持其原始价值。
鉴于上述事实以及我的原始示例是在Section 3.9 Types
默认为char
的英特尔计算机上,我仍然不相信signed char
是否应优先于unsigned char
}。
还有别的吗?
答案 0 :(得分:82)
在C中,unsigned char
数据类型是唯一同时具有以下三个属性的数据类型
如果这些是您正在寻找的“二进制”数据类型的属性,那么您最终应该使用unsigned char
。
对于第二个属性,我们需要一个unsigned
的类型。对于这些,所有转换都是使用模数arihmetic定义的,在大多数99%的架构中,这里模UCHAR_MAX+1
,256
。所有将较宽值转换为unsigned char
因此只对应于截断到最低有效字节。
另外两种字符类型通常不起作用。无论如何,signed char
已签名,因此不适合转换不适合它的值。 char
并未修复为已签名或未签名,但在您的代码移植到的特定平台上,即使它未经签名也可以签名。
答案 1 :(得分:12)
普通char
类型存在问题,不应该用于除字符串之外的任何内容。 char
的主要问题是您无法知道它是有符号还是无符号:这是实现定义的行为。这使char
与int
等不同,int
始终保证已签名。
虽然VC发出警告......截断常数值
它告诉您正在尝试将int文字存储在char变量中。这可能与签名有关:如果您尝试存储值为>的整数0x7F在签名字符内,可能会发生意外情况。形式上,这是C中的未定义行为,但实际上如果尝试将结果打印为存储在(带符号)char中的整数值,则只会得到一个奇怪的输出。
在这种特定情况下,警告无关紧要。
编辑:
在其他相关问题中,unsigned char被突出显示,因为它是唯一的(字节/最小)数据类型,保证C规范没有填充。
理论上,除了unsigned char和signed char之外的所有整数类型都允许包含“填充位”,根据C11 6.2.6.2:
“对于unsigned char以外的无符号整数类型,其位数为 对象表示应分为两组:值位和 填充位(不需要任何后者)。“
“对于有符号整数类型,对象表示的位应为 分为三组:值位,填充位和符号 位。不需要任何填充位;签名字母不得 任何填充位。“
C标准故意模糊和模糊,允许这些理论填充位,因为:
但是,在C标准之外的现实世界中,以下适用:
所以没有真正的理由使用unsigned char或signed char来躲避C标准中的一些理论场景。
答案 2 :(得分:12)
在比较单个字节的内容时,您将获得大部分问题:
char c[5];
c[0] = 0xff;
/*blah blah*/
if (c[0] == 0xff)
{
printf("good\n");
}
else
{
printf("bad\n");
}
可以打印“坏”,因为,根据你的编译器,c [0]将符号扩展为-1,这与0xff没有任何相同之处
答案 3 :(得分:5)
字节通常用作无符号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,该值不能在签名字符中表示(范围-128到+127),因此它将被转换为已签名值(-16)。
将char声明为signed将删除警告,并且在没有任何警告的情况下进行干净构建总是很好。
答案 4 :(得分:4)
普通char
类型的签名是实现定义的,所以除非您实际处理字符数据(使用平台字符集的字符串 - 通常是ASCII),通常最好指定签名-ness使用signed char
或unsigned char
明确显示。
对于二进制数据,最好的选择很可能是unsigned char
,特别是如果将对数据执行按位运算(特别是位移,对于有符号类型和无符号类型的行为不相同)。
答案 5 :(得分:2)
我在问为什么看起来像char一样好的东西应该输入unsigned char?
如果你做的事情在标准意义上不是“正确的”,你依赖于未定义的行为。您的编译器可能会按照您希望的方式执行此操作,但您不知道它明天会做什么。你不知道GCC做什么或VC ++ 2012.或者即使行为取决于外部因素或调试/发布编译等。一旦你离开标准的安全路径,你可能会遇到麻烦。
答案 6 :(得分:2)
嗯,你怎么称呼“二进制数据”?这是一堆比特,没有任何意义由称为“二进制数据”的软件的特定部分分配给它们。什么是最接近的原始数据类型,它传达了对这些位中的任何一个缺乏任何特定含义的想法?我想unsigned char
。
答案 7 :(得分:2)
是否真的有必要使用unsigned char来保存二进制数据,就像在一些处理字符编码或二进制缓冲区的库中一样?
“真的”必要吗?否。
虽然这是一个非常好的主意,但有很多原因。
您的示例使用的是printf,它不是类型安全的。也就是说,printf从格式字符串中获取格式化提示,而不是数据类型。您可以轻松尝试:
printf("%s\n", (void*)c);
......结果会一样。如果你用c ++ iostreams做同样的事情,结果会有所不同(取决于c的签名)。
什么推理可能会提倡使用unsigned char而不是普通的char?
无符号指定数据的最高有效位(对于无符号字符的第8位)表示符号。由于您显然不需要,您应该指定您的数据是无符号的(“符号”位表示数据,而不是其他位的符号)。