C / C ++为什么对二进制数据使用无符号字符?
是否真的有必要像在某些字符编码或二进制缓冲区中使用的库中那样,使用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);
这两个char都正确输出了signed char,其中unsigned char是十六进制的Unicode代码点char的编码。
甚至char也正确复制了char保留的位。
有什么理由可以主张使用char而不是signed char?
在其他相关问题中,突出显示char,因为它是唯一(字节/最小)数据类型,可以通过C规范保证没有填充。 但是,如以上示例所示,输出似乎不受任何填充的影响。
我已经使用VC ++ Express 2010和MinGW来编译以上内容。 虽然VC给出了警告
char
输出似乎没有反映出这一点。
附言 这可能被标记为可能的重复项。字节缓冲区应该是带符号的还是无符号的char缓冲区? 但我的意图是不同的。 我在问为什么应该在char上正常工作呢?
更新:要引用N3337,
char
2对于琐碎的任何对象(基类子对象除外) 可复制的类型T,对象是否持有类型的有效值 T,组成对象的基础字节(1.7)可以复制到 char或unsigned char的数组。 如果内容为char数组 或将未签名的字符复制回对象,则对象应 随后保留其原始值。
鉴于上述事实,并且我的原始示例是在Intel计算机上,其char默认为signed char,因此仍然不确定是否应将unsigned char优先于char。
还要别的吗?
8个解决方案
84 votes
在C中,signed char数据类型是唯一同时具有以下所有三个属性的数据类型
它没有填充位,即所有存储位都对数据值有贡献的位置
从该类型的值开始的按位运算,当转换回该类型时,不会产生溢出,陷阱表示或未定义的行为
它可以在不违反“别名规则”的情况下别名其他数据类型,也就是说,将保证通过不同类型的指针访问同一数据
如果这些是您要查找的“二进制”数据类型的属性,则应绝对使用signed char。
对于第二个属性,我们需要一种类型为signed char的类型。对于所有这些转换,均使用模数算法定义,在大多数99%的体系结构中,此处取模char、256。 所有较宽值到unsigned char的所有转换仅对应于截断最低有效字节。
其他两个字符类型通常不能相同。 signed char无论如何都已签名,因此无法正确定义不适合它的值的转换。 char并非固定为已签名或未签名,但在将您的代码移植到的特定平台上,即使未在您的代码上未签名,也可能已签名。
Jens Gustedt answered 2020-02-04T17:33:09Z
13 votes
比较单个字节的内容时,您会遇到大多数问题:
char c[5];
c[0] = 0xff;
/*blah blah*/
if (c[0] == 0xff)
{
printf("good\n");
}
else
{
printf("bad\n");
}
可以打印“ bad”,因为根据您的编译器,c [0]将被符号扩展为-1,这与0xff完全不同
Tom Tanner answered 2020-02-04T17:33:34Z
12 votes
普通的char类型是有问题的,除了字符串之外,不应用于任何其他类型。 char的主要问题在于,您不知道它是签名的还是未签名的:这是实现定义的行为。 这使得char与int等有所不同,始终保证int是已签名的。
尽管VC发出警告...常量值被截断
它告诉您您正在尝试将int文字存储在char变量中。 这可能与签名有关:如果尝试在签名字符中存储值> 0x7F的整数,则可能会发生意外情况。 形式上,这是C语言中未定义的行为,尽管实际上,如果尝试将结果打印为存储在(有符号)char中的整数值,则只会得到一个奇怪的输出。
在这种情况下,警告无关紧要。
编辑:
在其他相关问题中,突出显示了无符号字符,因为它是唯一(字节/最小)数据类型,可以通过C规范保证没有填充。
理论上,按照C11 6.2.6.2,除无符号字符和有符号字符外,所有整数类型都允许包含“填充位”:
“对于除无符号字符以外的无符号整数类型, 对象表示应分为两组:值位和 填充位(后者不需要任何)。”
“对于有符号整数类型,对象表示的位应 分为三组:值位,填充位和符号 一点。 不需要任何填充位; 签名的字符不应具有 任何填充位。”
C标准故意模糊不清,允许使用这些理论填充位,因为:
它允许使用与标准8位符号表不同的符号表。
它允许实现定义的有符号性和奇怪的有符号整数格式,例如一个人的补码或“符号和大小”。
整数不一定会使用分配的所有位。
但是,在C标准之外的现实世界中,以下条件适用:
符号表几乎可以肯定是8位(UTF8或ASCII)。 存在一些奇怪的异常,但是干净的实现在实现大于8位的符号表时使用标准类型wchar_t。
签名始终是二进制的补充。
整数始终使用分配的所有位。
因此,没有真正的理由使用无符号字符或有符号字符只是为了躲避C标准中的某些理论情况。
Lundin answered 2020-02-04T17:35:11Z
6 votes
字节通常是无符号的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将删除警告,并且始终保持良好的构建而没有任何警告总是很好。
Paolo Brandoli answered 2020-02-04T17:35:49Z
4 votes
普通unsigned char类型的签名是由实现定义的,因此,除非您实际处理字符数据(使用平台的字符集的字符串-通常为ASCII),通常最好通过使用signed char明确指定签名 或unsigned char。
对于二进制数据,最佳选择最有可能是unsigned char,尤其是如果将对数据执行按位运算(特别是移位,这种行为与有符号类型的行为与无符号类型的行为不同)。
Sander De Dycker answered 2020-02-04T17:36:14Z
2 votes
我问为什么应该用unsigned char键入似乎可以正常工作的字符?
如果您做的事情不是标准意义上的“正确”,那么您将依赖未定义的行为。 您的编译器可能会按照您今天想要的方式执行此操作,但是您不知道明天会执行什么操作。 您不知道GCC或VC ++ 2012的功能。或者即使行为取决于外部因素或Debug / Release编译等。一旦离开标准的安全路径,也可能会遇到麻烦。
Philipp answered 2020-02-04T17:36:39Z
2 votes
那么,您怎么称呼“二进制数据”? 这是一堆比特,没有任何意义的软件将其称为“二进制数据”。 最接近的原始数据类型是什么,它向这些位中的任何一位传达了缺乏任何特定含义的想法? 我认为unsigned char。
chill answered 2020-02-04T17:36:59Z
2 votes
像某些在字符编码或二进制缓冲区上工作的库中一样,真的需要使用无符号字符来保存二进制数据吗?
“真的”有必要吗? 没有。
但是,这是一个非常好的主意,并且有很多原因。
您的示例使用printf,它不是类型安全的。 也就是说,printf从格式字符串而不是数据类型中获取格式提示。 您可以轻松地尝试:
printf("%s\n", (void*)c);
...结果将是相同的。 如果使用c ++ iostream尝试相同的操作,结果将有所不同(取决于c的带符号性)。
有什么推理可能会提倡使用无符号字符而不是纯字符?
Unsigned指定数据的最高有效位(对于unsigned char,第8位)表示符号。 由于您显然不需要这样做,因此应指定数据是无符号的(“符号”位表示数据,而不是其他位的符号)。
utnapistim answered 2020-02-04T17:37:46Z