types转换 – 无符号到signed int / char
我试着执行下面的程序:
#include <stdio.h> int main() { signed char a = -5; unsigned char b = -5; int c = -5; unsigned int d = -5; if (a == b) printf("\r\n char is SAME!!!"); else printf("\r\n char is DIFF!!!"); if (c == d) printf("\r\n int is SAME!!!"); else printf("\r\n int is DIFF!!!"); return 0; }
对于这个程序,我得到的输出:
char是DIFF! int是相同的!
为什么我们得到不同的输出?
应该如下输出?
char是相同的! int是相同的!
键盘链接 。
这是因为C中的各种隐式types转换规则.C程序员必须知道其中的两个: 通常的算术转换和整数提升 (后者是前者的一部分)。
在char的情况下,你有types(signed char) == (unsigned char)
。 这些都是小整数types 。 其他这样的小整数types是bool
和short
。 整数提升规则规定 ,每当一个小整数types是一个操作的操作数,它的types将被提升为int
,这是signed。 无论签名还是未签名,都会发生这种情况。
在有signed char
的情况下,该符号将被保留,并且将被提升为包含值-5的int
。 在unsigned char
的情况下,它包含一个值为251(0xFB)。 它将被提升为一个包含相同值的int
。 你结束了
if( (int)-5 == (int)251 )
在整数情况下,你有types(signed int) == (unsigned int)
。 它们不是小整数types,所以整数提升不适用。 相反,它们被通常的算术转换所平衡,即如果两个操作数具有相同的“等级”(大小)但具有不同的符号性,则将有符号的操作数转换为与无符号操作数相同的types。 你结束了
if( (unsigned int)-5 == (unsigned int)-5)
很酷的问题!
int
比较是有效的,因为两个int都包含完全相同的位,所以它们本质上是相同的。 但是char
呢?
嗯,C在各种场合下都暗中鼓吹char
。 这是其中之一。 你的代码说if(a==b)
,但是编译器实际上是这么做的:
if((int)a==(int)b)
(int)a
是-5,但是(int)b
是251.这些绝对不一样。
编辑:作为@ Carbonic酸指出, (int)b
是251只有一个char
是8位长。 如果int
是32位长, (int)b
是-32764。
REDIT:如果一个字节不是8位,那么讨论答案的性质时有一大堆评论。 这种情况唯一的区别是(int)b
不是251,而是一个不同的正数,而不是-5。 这与现在仍然非常酷的问题无关。
欢迎来到整数推广 。 如果我可以从网站引用:
如果int可以表示原始types的所有值,则该值将被转换为int; 否则,它被转换为一个无符号的整数。 这些被称为整数促销。 所有其他types均不受整数升级的影响。
当你做这样的比较的时候,C可能会让你感到困惑,我最近对一些非C编程的朋友感到困惑,
#include <stdio.h> #include <string.h> int main() { char* string = "One looooooooooong string"; printf("%d\n", strlen(string)); if (strlen(string) < -1) printf("This cannot be happening :("); return 0; }
这确实打印This cannot be happening :(
看似25表明小于-1!
然而,下面发生的是,-1被表示为无符号整数,由于在32位系统上由于基础位表示而等于4294967295。 自然25比4294967295小。
但是,如果我们将由strlen
返回的size_t
types明确地转换为有符号整数:
if ((int)(strlen(string)) < -1)
那么它将比较25和-1,一切都将与世界。
一个好的编译器应该警告你一个无符号和有符号整数之间的比较,但它仍然很容易错过(特别是如果你不启用警告)。
这对于Java程序员来说尤其令人困惑,因为所有原始types都被签名了。 这就是James Gosling(Java的创造者之一) 在这个话题上所说的话 :
Gosling:对于我这样一个语言devise师来说,我真的不把自己算在这些日子里,真正意义上的“简单”意味着什么,我可以指望J.Random Developer在他的脑海中保持这个规范。 这个定义说,例如,Java并不是 – 实际上很多这些语言最终都会遇到大量的angular落案例,这些都是没有人真正理解的。 测验任何C开发者关于未签名的,很快你会发现几乎没有C开发者真正理解什么是无符号的,什么是无符号的算术。 像这样的事情使C复杂。 Java的语言部分我觉得很简单。 你必须查找库。
-5
的hex表示是:
- 8位,2的补码有
signed char
:0xfb
- 32位,二进制补码
signed int
:0xfffffffb
当你把一个有符号的数字转换成一个无符号的数字,反之亦然,编译器确实没有任何东西。 那有什么可做的 这个数字要么是可转换的,要么是不可转换的,在这种情况下,未定义的或者实现定义的行为会出现(我没有真正检查过),最有效的实现定义的行为是什么也不做。
所以, (unsigned <type>)-5
的hex表示是:
- 8位,
unsigned char
:0xfb
- 32位,
unsigned int
:0xfffffffb
眼熟? 它们与签名版本相同。
当你编写if (a == b)
,其中a
和b
的types是char
,编译器实际上需要读取的是if ((int)a == (int)b)
。 (这是所有人都在喋喋不休的“整数推广”。)
那么,当我们将char
转换为int
时会发生什么?
- 8位有
signed char
到32位有signed int
:0xfb
– >0xfffffffb
- 那么,这是有道理的,因为它匹配
-5
以上的表示! - 它被称为“符号扩展”,因为它将字节的最高位“符号位”向左复制到新的更宽的值。
- 那么,这是有道理的,因为它匹配
- 8位
unsigned char
到32位有signed int
:0xfb
– >0x000000fb
- 这次它做了一个“零扩展”,因为源types是无符号的 ,所以没有符号位要复制。
所以, a == b
确实是0xfffffffb == 0x000000fb
=>不匹配!
而且, c == d
确实是0xfffffffb == 0xfffffffb
=>匹配!
我的观点是:在编译时你没有得到一个警告“比较签名和无符号expression式”吗?
编译器试图告诉你他有权做些疯狂的事情! :)我会补充说,疯狂的东西会发生使用大的值,接近原始types的能力。 和
unsigned int d = -5;
为d分配一个很大的价值,它是等价的(即使可能不能保证等价)是:
unsigned int d = UINT_MAX -4; ///Since -1 is UINT_MAX
编辑:
但是,有意思的是只有第二个比较给出警告(检查代码) 。 所以这意味着应用转换规则的编译器确信在比较unsigned char
和char
时不会有错误(在比较过程中它们将被转换为可以安全地表示其所有可能值的types)。 而他在这一点上是正确的。 然后,它会通知您, unsigned int
和int
不会是这种情况:在比较过程中,2中的一个将被转换为无法完全表示它的types。
为了完整起见, 我也简单地检查了一下 :编译器的行为与字符的行为相同,并且正如所期望的那样,在运行时没有错误。
。
关于这个话题,我最近问了这个问题 (但是面向C ++)。