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是boolshort整数提升规则规定 ,每当一个小整数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_ttypes明确地转换为有符号整数:

 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 char0xfb
  • 32位,二进制补码signed int0xfffffffb

当你把一个有符号的数字转换成一个无符号的数字,反之亦然,编译器确实没有任何东西。 那有什么可做的 这个数字要么是可转换的,要么是不可转换的,在这种情况下,未定义的或者实现定义的行为会出现(我没有真正检查过),最有效的实现定义的行为是什么也不做。

所以, (unsigned <type>)-5的hex表示是:

  • 8位, unsigned char0xfb
  • 32位, unsigned int0xfffffffb

眼熟? 它们与签名版本相同。

当你编写if (a == b) ,其中ab的types是char ,编译器实际上需要读取的是if ((int)a == (int)b) 。 (这是所有人都在喋喋不休的“整数推广”。)

那么,当我们将char转换为int时会发生什么?

  • 8位有signed char到32位有signed int0xfb – > 0xfffffffb
    • 那么,这是有道理的,因为它匹配-5以上的表示!
    • 它被称为“符号扩展”,因为它将字节的最高位“符号位”向左复制到新的更宽的值。
  • 8位unsigned char到32位有signed int0xfb – > 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 charchar时不会有错误(在比较过程中它们将被转换为可以安全地表示其所有可能值的types)。 而他在这一点上是正确的。 然后,它会通知您, unsigned intint不会是这种情况:在比较过程中,2中的一个将被转换为无法完全表示它的types。

为了完整起见, 我也简单地检查了一下 :编译器的行为与字符的行为相同,并且正如所期望的那样,在运行时没有错误。

关于这个话题,我最近问了这个问题 (但是面向C ++)。