类型转换-无符号到有符号的 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 是一样的! ! !

代码存储器链路.

32873 次浏览

问得好!

int比较起作用了,因为两个 int 包含完全相同的位,所以它们本质上是相同的。那 char呢?

啊,C 在不同的场合含蓄地把 char提升到 int。这是其中之一。你的代码写的是 if(a==b),但是编译器实际上把它转换为:

if((int)a==(int)b)

(int)a是 -5,但是 (int)b是251。这两个绝对不一样。

编辑: 正如@Carbon-Acid 指出的,只有当 char为8位长时,(int)b才是251。如果 int为32位长,则 (int)b为 -32764。

REDIT: 如果一个字节不是8位长,那么就会有一大堆评论讨论这个问题的本质。在这种情况下,唯一的区别是 (int)b不是251,而是一个不同的 确定数字,它不是 -5。这个问题和现在还很酷的问题没有太大关系。

欢迎来到 整数升级。请允许我引用网站上的话:

如果 int 可以表示原始类型的所有值,则该值为 转换为整型; 否则,转换为无符号整型。 这些被称为整数促销。所有其他类型是不变的 通过整数促销。

当你进行这样的比较时,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类型强制转换为有符号整数:

if ((int)(strlen(string)) < -1)

然后,它将比较25对 -1,一切都将与世界。

一个好的编译器应该警告您无符号整数和有符号整数之间的比较,但是它仍然很容易被忽略(特别是如果您不启用警告)。

这对于 Java 程序员来说尤其令人困惑,因为所有的基本类型都是带有符号的。下面是 James Gosling (Java 的创造者之一)的 不得不就这个问题发表意见:

高斯林: 对于我这个语言设计师来说,我并没有把它算在内 我自己这些天来,“简单”真正结束的意思是可以 我希望随机开发者把规格说明书记在脑子里 定义表明,例如,Java 不是——实际上很多 这些语言最终会带来很多无人问津的问题 真正理解。测试任何 C 开发人员关于未签名和漂亮 很快你就会发现几乎没有 C 开发人员真正理解 无符号算术是什么,诸如此类的东西 我认为 Java 的语言部分非常简单。 你必须查阅的图书馆。

这是因为 C 语言中的各种隐式类型转换规则。C 程序员必须知道其中的两个: 通常的算术转换整数提升(后者是前者的一部分)。

在 char 的情况下,类型为 (signed char) == (unsigned char)。这些都是 小整数类型。其他这样的小整数类型是 boolshort整数升级规则整数升级规则的状态是,每当一个小整数类型是一个操作的操作数时,它的类型将被提升到 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)

我的观点是: 您在编译时没有收到“比较有符号和无符号表达式”的警告吗?

编译器试图告诉你,他有权做疯狂的事情!:)我会补充说,疯狂的事情会发生,使用大值,接近原始类型的容量。还有

 unsigned int d = -5;

如果给 d 赋一个很大的值,它就等于(即使,可能不保证是等价的) :

 unsigned int d = UINT_MAX -4; ///Since -1 is UINT_MAX

编辑:

然而,有趣的是注意到只有第二个比较给出了警告 (检查代码)。因此,这意味着应用转换规则的编译器确信在 unsigned charchar之间的比较中不会出现错误(在比较期间,它们将被转换为能够安全地表示其所有可能值的类型)。他在这一点上是正确的。然后,它通知您 unsigned intint不是这种情况: 在比较期间,2中的一个将被转换为不能完全表示它的类型。

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

.

关于这个主题,我最近询问了 这个问题(然而,是面向 C + + 的)。

-5的十六进制表示法是:

  • 8位,2的补码 signed char: 0xfb
  • 32位,2的补码 signed int: 0xfffffffb

当你把一个有符号的数字转换成一个无符号的数字,或者反过来,编译器就什么都不做了。有什么办法呢?这个数字要么是可转换的,要么是不可转换的,在这种情况下,未定义的或实现定义的行为随之而来(我实际上并没有检查哪个) ,而最有效的实现定义的行为是什么都不做。

因此,(unsigned <type>)-5的十六进制表示是:

  • 8位 unsigned char: 0xfb
  • 32位 unsigned int: 0xfffffffb

看起来眼熟吗? 它们和签名版本一模一样。

当编写 if (a == b)时,其中 ab的类型为 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
    • 这一次它执行了“零扩展”,因为源类型是 没签名,所以没有符号位可以复制。

所以,a == b确实是 0xfffffffb == 0x000000fb = > 不匹配!

而且,c == d确实做到了 0xfffffffb == 0xfffffffb = > 匹配!