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

时间:2012-11-30 09:33:40

标签: c++ c character-encoding bytebuffer rawbytestring

是否真的有必要使用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 }。

还有别的吗?

8 个答案:

答案 0 :(得分:82)

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

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

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

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

另外两种字符类型通常不起作用。无论如何,signed char已签名,因此不适合转换不适合它的值。 char并未修复为已签名或未签名,但在您的代码移植到的特定平台上,即使它未经签名也可以签名。

答案 1 :(得分:12)

普通char类型存在问题,不应该用于除字符串之外的任何内容。 char的主要问题是您无法知道它是有符号还是无符号:这是实现定义的行为。这使charint等不同,int始终保证已签名。

  

虽然VC发出警告......截断常数值

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

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

编辑:

  

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

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

  

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

     

“对于有符号整数类型,对象表示的位应为   分为三组:值位,填充位和符号   位。不需要任何填充位;签名字母不得   任何填充位。“

C标准故意模糊和模糊,允许这些理论填充位,因为:

  • 它允许使用与标准8位符号表不同的符号表。
  • 它允许实现定义的签名和奇怪的有符号整数格式,例如一个补码或“符号和幅度”。
  • 整数可能不一定使用所有已分配的位。

但是,在C标准之外的现实世界中,以下适用:

  • 符号表几乎肯定是8位(UTF8或ASCII)。存在一些奇怪的异常,但在实现大于8位的符号表时,干净的实现使用标准类型 wchar_t
  • 签名永远是两个补充。
  • 整数始终使用分配的所有位。

所以没有真正的理由使用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 charunsigned 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位)表示符号。由于您显然不需要,您应该指定您的数据是无符号的(“符号”位表示数据,而不是其他位的符号)。