我尝试执行以下程序:
#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是SAME !!!
为什么我们两者都有不同的输出?
输出应该如下吗?
char是相同的! int是相同的!
A codepad link。
答案 0 :(得分:80)
这是因为C中的各种隐式类型转换规则.C程序员必须知道其中两个: the usual arithmetic conversions 和整数促销(后者是前者的一部分)。
在char案例中,您有类型(signed char) == (unsigned char)
。这些都是小整数类型。其他此类小整数类型为bool
和short
。 整数提升规则表明,只要小整数类型是操作的操作数,其类型就会被提升为int
,这是有符号的。无论类型是签名还是未签名,都会发生这种情况。
如果是signed char
,则会保留该符号,并将其提升为包含值-5的int
。在unsigned char
的情况下,它包含一个值251(0xFB)。它将被提升为包含相同值的int
。你最终得到了
if( (int)-5 == (int)251 )
在整数的情况下,您有类型(signed int) == (unsigned int)
。它们不是小整数类型,因此整数促销不适用。相反,它们通过通常的算术转换来平衡,它表明如果两个操作数具有相同的“rank”(大小)但签名不同,则签名操作数将转换为与未签名操作数相同的类型。你最终得到了
if( (unsigned int)-5 == (unsigned int)-5)
答案 1 :(得分:36)
很酷的问题!
int
比较有效,因为两个int都包含完全相同的位,因此它们基本相同。但是char
s呢?
啊,C在不同场合暗中将char
提升为int
。这是其中之一。您的代码显示if(a==b)
,但编译器实际上将其转换为:
if((int)a==(int)b)
(int)a
为-5,但(int)b
为251.这些肯定不一样。
编辑:正如@ Carbonic-Acid指出的那样,(int)b
仅在char
为8位长时才为251。如果int
长度为32位,则(int)b
为-32764。
REDIT:如果一个字节长度不是8位,那么就会有很多评论讨论答案的本质。在这种情况下,唯一的区别是(int)b
不是251,而是正数字,不是-5。这与仍然非常酷的问题无关。
答案 2 :(得分:21)
欢迎来到integer promotion。如果我可以从网站引用:
当你进行这些比较时,C可能会让人感到困惑,我最近对以下挑逗的一些非C编程朋友感到困惑:如果int可以表示原始类型的所有值,则值为 转换为int;否则,它将转换为unsigned int。 这些被称为整数促销。所有其他类型都保持不变 通过整数促销。
#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。
如果我们明确地将size_t
返回的strlen
类型转换为有符号整数:
if ((int)(strlen(string)) < -1)
然后它会将25与-1进行比较,所有这些都与世界相符。
一个好的编译器应该警告你有关无符号整数和有符号整数之间的比较,但它仍然很容易被遗漏(特别是如果你不启用警告)。
这对Java程序员来说尤其令人困惑,因为所有原始类型都有签名。以下是James Gosling(Java的创建者之一)had to say on the subject:
戈斯林:对我来说,作为一名语言设计师,我并不重视 就像现在这样,“简单”真正意味着什么才有可能 我希望J. Random Developer能够掌握这个规范。那 定义说,例如,Java不是 - 实际上很多 这些语言最终会出现很多不完整的案例 真的了解。测试任何C开发人员关于unsigned和漂亮的 很快你就会发现几乎没有C开发人员真正理解什么 继续使用无符号,无符号算术。像这样的东西 使C复杂。我认为Java的语言部分非常简单。 你必须查找的库。
答案 3 :(得分:11)
-5
的十六进制表示形式为:
signed char
:0xfb
signed int
:0xfffffffb
当您将有符号数转换为无符号数时,反之亦然,编译器确实......没有任何内容。怎么办?数字是可转换的,或者不是,在这种情况下,未定义或实现定义的行为如下(我实际上没有检查过哪个),并且最有效的实现定义行为是什么都不做。
因此,(unsigned <type>)-5
的十六进制表示为:
unsigned char
:0xfb
unsigned int
:0xfffffffb
看起来很熟悉?它们与签名版本的位数相同。
当您编写if (a == b)
,其中a
和b
的类型为char
时,编译器实际需要读取的内容为if ((int)a == (int)b)
。 (这是其他人正在进行的“整数推广”。)
那么,当我们将char
转换为int
时会发生什么?
signed char
到32位signed int
:0xfb
- &gt; 0xfffffffb
-5
的表示形式!unsigned char
到32位signed int
:0xfb
- &gt; 0x000000fb
所以,a == b
确实0xfffffffb == 0x000000fb
=&gt;没有比赛!
并且,c == d
确实0xfffffffb == 0xfffffffb
=&gt;匹配!
答案 4 :(得分:1)
我的观点是:你没有在编译时收到“比较有符号和无符号表达式”的警告吗?
编译器试图通知您他有权做疯狂的事情! :)我想补充一点,疯狂的东西会发生使用大值,接近原始类型的容量。和
unsigned int d = -5;
肯定为d赋予一个很大的值,它是等价的(即使,可能不保证是等价的):
unsigned int d = UINT_MAX -4; ///Since -1 is UINT_MAX
修改强>
然而,有趣的是注意到只有第二次比较会发出警告(check the code)。因此,这意味着应用转换规则的编译器确信在unsigned char
和char
之间的比较中不会出现错误(在比较期间,它们将转换为可以安全地表示其所有内容的类型可能的价值观)。他在这一点上是对的。然后,它通知您unsigned int
和int
不会出现这种情况:在比较期间,其中一个将转换为无法完全代表它的类型。
为了完整性,I checked it also for short:编译器的行为方式与字符相同,并且正如预期的那样,在运行时没有错误。
与此主题相关,我最近问过this question(还有,面向C ++)。