C / C ++为什么要使用二进制数据的无符号字符?

是否真的有必要使用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 (𤭢)的hex编码。

即使是memcpy也正确地复制了char所保存的位。

什么推理可能主张使用unsigned char而不是plain char

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

我用VC ++ Express 2010和MinGW来编译上面的代码。 尽pipeVC发出警告

warning C4309: '=' : truncation of constant value

输出似乎并没有反映出来。

PS这可能被标记为一个可能的重复如果一个缓冲区的字节被签名或无符号字符缓冲区? 但我的意图是不同的。 我问为什么似乎工作正常与char应该键入unsigned char

更新:从N3337引用,

Section 3.9 Types

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

鉴于上述事实,我原来的例子是在英特尔机器上的char默认为有signed char ,我仍然不相信,如果unsigned char应优先于char

还要别的吗?

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

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

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

对于第二个属性,我们需要一个unsigned的types。 对于这些所有的转换都是用模arihmetic来定义的,这里最模式的UCHAR_MAX+1在99%的体系结构中。 所有将较宽的值转换为unsigned char从而只对应于截断到最低有效字节。

其他两种字符types通常不会相同。 无论如何, signed char被签名,所以不适合的值的转换不是很好的定义。 char不是固定的,不能被签名或未签名,但是在你的代码移植到的特定平台上,它可能会被签名,即使它没有签名。

简单的chartypes是有问题的,不应该用于任何东西,但string。 char的主要问题是你无法知道它是有符号还是无符号的:这是实现定义的行为。 这使得charint不同, int总是保证被签名。

尽pipeVC给出了警告…截断的恒定值

它告诉你,你正试图在charvariables中存储int文字。 这可能与签名有关:如果尝试在有符号字符内存储值> 0x7F的整数,则可能会发生意想不到的情况。 forms上,这在C中是未定义的行为,尽pipe实际上如果试图将结果作为存储在(有符号)字符中的整数值进行打印,您只会得到一个奇怪的输出。

在这个特定的情况下,警告应该不重要。

编辑:

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

理论上,除unsigned char和signed char以外的所有整数types都允许包含“填充位”,如C11 6.2.6.2所示:

“对于无符号字符以外的无符号整数types,对象表示的位应该被分成两组:值位和填充位(不需要后者中的任何一个)。

对于带符号整数types,对象表示的位应该被分成三组:值位,填充位和符号位,不需要任何填充位; signed char不应该有任何填充位。

C标准有意含糊而模糊,允许这些理论填充位,因为:

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

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

  • 符号表几乎肯定是8位(UTF8或ASCII)。 一些奇怪的例外存在,但是当实现大于8位的符号表时,干净的实现使用标准typeswchar_t
  • 签名永远是二的补充。
  • 一个整数总是使用所有分配的位。

所以没有真正的理由使用unsigned char或者signed char来避开C标准中的一些理论场景。

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

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

可以打印“坏”,因为,取决于你的编译器,c [0]将符号扩展为-1,这不是任何方式相同的0xff

字节通常用作无符号的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声明为已签名将删除该警告,并且始终有一个干净的生成没有任何警告。

简单chartypes的符号是由实现定义的,所以除非实际处理字符数据(使用平台字符集的string – 通常是ASCII),通常最好使用signed char明确指定signed charunsigned char

对于二进制数据,最好的select是最可能的unsigned char ,尤其是对数据执行按位操作(特别是位移,对于无符号types,这对于有符号types来说并不相同)。

我问为什么似乎工作正常与char应该键入无符号字符?

如果你在标准意义上做的事情不是“正确的”,那么你就依赖于未定义的行为。 你的编译器可能会按照你现在想要的方式来做,但你不知道它明天会发生什么。 你不知道什么是GCC或VC ++ 2012,或者即使行为依赖于外部因素或debugging/发布编译等。只要你离开标准的安全path,你可能会遇到麻烦。

那么,你叫什么“二进制数据”? 这是一堆,没有任何意义的软件称为“二进制数据”的特定部分分配给他们。 什么是最接近的原始数据types,它传达了这些比特中没有任何特定含义的想法? 我认为unsigned char

是否真的有必要使用无符号字符来保存二进制数据,如在字符编码或二进制缓冲区工作的一些库?

“真的”有必要吗? 没有。

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

你的例子使用printf,它不是types安全的。 也就是说,printf是从格式string中取出格式的,而不是从数据types中取出格式。 您可以轻松尝试:

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

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

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

无符号指定数据的最高有效位(对于第8位无符号字符)表示符号。 既然你显然不需要这个,你应该指定你的数据是无符号的(“符号”位表示数据,而不是其他位的符号)。