类型转换-未签名到已签名的int / char


73

我尝试执行以下程序:

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

字符是DIFF !!!诠释是相同的!

为什么我们为两者获得不同的输出?
输出应该如下吗?

字符是相同的!诠释是相同的!

一个键盘连接


22
隐式整数提升再次来袭!
Mysticial 2013年

详细说明在这里
伦丁

Answers:


82

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

在char情况下,您具有类型(signed char) == (unsigned char)。这些都是小整数类型。其他此类小整数类型是boolshort。该整数提升规则的状态,每当一个小的整数类型是操作的操作数,它的类型将得到提升到int,这是签署。无论类型是带符号还是无符号,都将发生这种情况。

如果是signed char,则将保留符号并将其提升为int包含值-5的符号。在中unsigned char,它包含的值为251(0xFB)。它将被提升为int包含相同值的值。你最终得到


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


6
略有误差:unsigned int如果int不够大,无法代表所有转化率较低类型的所有值,也可以提升为误差;例如,假定intshort均为16位类型;然后,转换unsigned shortint通常不能保留价值,所以我们一起去unsigned int代替
克里斯托夫

int如果我显式地将变量强制转换为,它会升级为if((unsigned char) 128 == (signed char) -128)吗?
2013年

@noufal是的。无法避免通过代码进行整数提升。您唯一可以做的就是将操作结果转换为预期的类型,这恰好是100%安全的。但是,只要编译器不改变结果,就可以优化升级。反过来,这意味着,如果您有非预期的副作用,例如无声签名更改,那么它们也会在优化的代码中出现。
伦丁

@伦丁:有没有保证(unsigned short)((unsigned short)65535u * (unsigned short)65535u)会产生1个而不是发射核弹头的保证?有什么方法可以计算两个16位数字乘积的低16位在16位计算机上是有效的,但在32位计算机上可以保证正确吗?
2014年

36

很酷的问题!

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

啊,C在各种情况下都会隐式地将chars提升为int。这就是其中之一。您的代码说if(a==b),但是编译器实际上将其转换为:

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

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

REDIT:如果一个字节的长度不是8位,那么有很多评论讨论了答案的性质。在这种情况下,唯一的区别(int)b是不是251,而是一个不同的数,不是-5。这与仍然很酷的问题并不真正相关。


4
是的,但我不想混淆OP。我要补充一点。
zmbq 2013年

11
在过去的40年中,您在哪里看到过8位以上的字节?
zmbq 2013年

6
@ user2522685因为C语言要求它,并且C语言不是理性,一致或逻辑的。
伦丁2013年

3
@zmbq:DSP不必每字节有8位,Unisys仍在大型机业务中,那里有一些怪异的Forth处理器(不过,它不需要C编译器来提供)-如果您看上去很努力,那么您仍然可以找到今天生产的此类系统
Christoph

3
还应注意,答案是误导性的-int比较不起作用,因为变量包含相同的位,而是因为它们的值在转换后比较相等;C语言几乎不关心表示形式-(unsigned)-1 == UINT_MAX即使使用符号幅度表示形式也是如此,与二进制补码相反,该转换不是小问题
Christoph

21

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

如果一个int可以表示原始类型的所有值,则该值将转换为int;否则,它将转换为unsigned int。这些称为整数促销。整数促销未更改所有其他类型。

当您进行诸如此类的比较时,C真的会让您迷惑不解,我最近对以下一些非C编程朋友感到困惑:

确实可以打印,This cannot be happening :(并且似乎表明25比-1小!

但是,在下面发生的是-1表示为无符号整数,由于其基础位表示形式,该整数在32位系统上等于4294967295。当然25比4294967295小。

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

然后它将25与-1比较,并且与世界一切都很好。

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

对于Java程序员而言,这尤其令人困惑,因为所有原始类型都已签名。这是James Gosling(Java的创建者之一)在这个问题上必须说的

高斯林:对于我作为语言设计师来说,这些天我并不算是真正的自己,“简单”最终的含义是我可以期望J. Random Developer掌握该规范。该定义表明,例如,Java并非如此-实际上,其中许多语言最终都带有很多极端情况,这是没人真正理解的。询问任何C开发人员有关无符号的知识,很快您就会发现几乎没有C开发人员真正了解无符号的含义,无符号的算法是什么。这样的事情使C变得复杂。我认为Java的语言部分非常简单。您必须查找的库。


1
Gosling的基本原理未能注意到BytePascal中的无符号类型不会带来任何困难。唯一有问题的无符号类型就是那些大于默认整数大小的类型。
2014年

我不明白为什么(int)(strlen(string)) < -1有效。您仅更改了比较的左侧,根据前面的说明,右侧仍然是4294967295。(cc @Xolve)
user13107

1
@ user13107strlen(string)返回的size_t结果不能表示为int。因此,比较的所有操作数都提升为unsigned int
Fabio Pozzi

10

的十六进制表示-5为:

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

当您将有符号数转换为无符号数时,反之亦然,编译器将不执行任何操作。该怎么办?该数字是可转换的,或者不是可转换的,在这种情况下,会出现未定义的或实现定义的行为(我尚未实际检查哪个),最有效的实现定义的行为是什么也不做。

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

  • 8位unsigned char0xfb
  • 32位,unsigned int0xfffffffb

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

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

所以,当我们转换会发生什么charint

  • 8位signed char到32位signed int0xfb->0xfffffffb
    • 好吧,这很有意义,因为它与-5上面的表示形式匹配!
    • 之所以称为“符号扩展”,是因为它将字节的最高位“符号位”向左复制到新的,更宽的值中。
  • 8位unsigned char到32位signed int0xfb->0x000000fb
    • 这次它执行“零扩展”,因为源类型是unsigned,因此没有要复制的符号位。

因此,a == b确实没有0xfffffffb == 0x000000fb=>不匹配!

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


根据C11 6.3.1.3/2"Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type."
Lundin

将无符号整数转换为带符号整数是明确定义或隐含的。定义,C11 6.3.1.3/1和6.3.1.3/3分别为:When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged./ - /"Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised."
Lundin的

@lundin有趣。尽管我猜想结果是我上面显示的结果,但我无法完全弄清楚重复添加或减去该类型的最大值多一个是什么意思。
2014年

1
这是一个解释。巧合的是,他们甚至以-5为例:)
Lundin 2014年

1

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

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

确实为d分配了一个大值,它是等效的(即使可能无法保证等效)为:

编辑:

但是,有趣的是,只有第二次比较才给出警告 (检查代码)。因此,这意味着应用转换规则的编译器有信心在unsigned char和之间char进行比较时不会出错(在比较期间,它们将被转换为可以安全表示其所有可能值的类型)。他在这一点上是正确的。然后,它通知您unsigned int和的情况并非如此int:在比较期间,2之一将转换为无法完全表示它的类型。

为了完整性,我也简短地检查了一下:编译器的行为与char的行为相同,并且正如预期的那样,运行时没有错误。

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


下注是因为...?知道原因将是建设性的。
安东尼奥

因为您没有回答问题?为什么它不能与char一起使用,而与int一起使用?
伦丁

@Lundin我的动机是这段代码触发了行为未定义的情况。不同的编译器将给出(有权给出)不同的结果。您可以尝试猜测为什么这个特定的编译器给出了这个结果,但是我认为这没有任何意义:应该避免未定义的行为,仅此而已。
安东尼奥

@Lundin而且,顺便说一句,它确实可以使用chars(它具有正确的行为),而不适用于int :)
Antonio
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.