73

我尝试执行以下程序:

#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;
}

对于这个程序,我得到了输出:

字符是不同的!!!int 是一样的!!!

为什么我们得到不同的输出?
输出应该如下?

字符是一样的!!!int 是一样的!!!

键盘链接。

4

5 回答 5

82

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

在 char 情况下,您有 types (signed char) == (unsigned char)。它们都是小整数类型。其他这样的小整数类型是boolshort整数提升规则规定,每当一个小整数类型是操作的操作数时,它的类型将被提升为带int符号的 。无论类型是有符号还是无符号,都会发生这种情况。

在 的情况下signed char,将保留符号并将其提升为int包含值 -5 的符号。在 的情况下unsigned char,它包含一个值为 251 (0xFB) 的值。它将被提升为int包含相同值的值。你最终得到

if( (int)-5 == (int)251 )

在整数情况下,您有 types (signed int) == (unsigned int)。它们不是小整数类型,因此整数促销不适用。相反,它们通过通常的算术转换来平衡,这表明如果两个操作数具有相同的“等级”(大小)但符号不同,则有符号操作数将转换为与无符号操作数相同的类型。你最终得到

if( (unsigned int)-5 == (unsigned int)-5)
于 2013-06-26T06:23:45.057 回答
36

很酷的问题!

比较int有效,因为两个整数都包含完全相同的位,所以它们本质上是相同的。但是chars呢?

啊,C在各种场合隐含地将 s 提升char为s。int这是其中之一。您的代码说if(a==b),但编译器实际上将其转换为:

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

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

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

编辑:如果一个字节不是 8 位长,会有一大堆评论讨论答案的性质。在这种情况下,唯一的区别(int)b是不是 251,而是一个不同的数,不是 -5。这与仍然非常酷的问题并不真正相关。

于 2013-06-26T06:02:44.617 回答
21

欢迎整数推广。如果我可以从网站上引用:

如果一个 int 可以表示原始类型的所有值,则将该值转换为 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。

但是,如果我们将size_t返回的类型显式转换strlen为有符号整数:

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

然后它将 25 与 -1 进行比较,一切都会好起来的。

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

这对于 Java 程序员来说尤其令人困惑,因为那里的所有原始类型都是有符号的。以下是 James Gosling(Java 的创造者之一)在这个问题上所说的话

Gosling:作为一名语言设计师,我现在并不认为自己是这样的,“简单”最终的真正含义是我能否期望 J. Random Developer 将规范牢牢记在脑海中。该定义表明,例如,Java 不是——事实上,这些语言中的许多最终都会出现很多极端情况,即没有人真正理解的事情。向任何 C 开发人员询问有关无符号的问题,很快你就会发现几乎没有 C 开发人员真正了解无符号是怎么回事,什么是无符号算术。这样的事情使 C 变得复杂。我认为 Java 的语言部分非常简单。您必须查找的库。

于 2013-06-26T06:13:09.793 回答
10

的十六进制表示-5为:

  • 8 位,二进制补码signed char0xfb
  • 32 位,二进制补码signed int0xfffffffb

当您将有符号数转换为无符号数时,或者反之亦然,编译器会……完全没有。有什么可做的?这个数字要么是可转换的,要么不是,在这种情况下,未定义或实现定义的行为会跟随(我实际上没有检查过哪个),最有效的实现定义的行为是什么都不做。

所以,十六进制表示(unsigned <type>)-5是:

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

看起来熟悉?它们与签名版本一点一点地相同。

当你写的时候if (a == b), whereabare 的类型char,编译器实际上需要读取的是什么if ((int)a == (int)b)。(这就是其他人都在谈论的“整数提升”。)

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

  • 8 位signed char到 32 位signed int0xfb->0xfffffffb
    • 嗯,这是有道理的,因为它与-5上面的表示相匹配!
    • 它被称为“符号扩展”,因为它将字节的最高位,即“符号位”,向左复制到新的更宽的值中。
  • 8 位unsigned char到 32 位signed int0xfb->0x000000fb
    • 这次它做了一个“零扩展”,因为源类型是unsigned,所以没有要复制的符号位。

所以,a == b真的0xfffffffb == 0x000000fb=> 不匹配!

而且,c == d真的0xfffffffb == 0xfffffffb=> 匹配!

于 2013-07-04T12:48:38.603 回答
1

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

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

 unsigned int d = -5;

为 d 分配了一个很大的值,它是等价的(即使,可能不保证是等价的)是:

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

编辑:

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

为了完整起见,我也简短地检查了它:编译器的行为方式与 chars 相同,并且正如预期的那样,运行时没有错误。

.

与这个主题相关,我最近问了这个问题(但是,面向 C++)。

于 2013-06-26T06:35:14.017 回答