为啥 0 < -0x80000000?

Posted

技术标签:

【中文标题】为啥 0 < -0x80000000?【英文标题】:Why is 0 < -0x80000000?为什么 0 < -0x80000000? 【发布时间】:2016-03-14 22:53:55 【问题描述】:

我有一个简单的程序:

#include <stdio.h>

#define INT32_MIN        (-0x80000000)

int main(void) 

    long long bal = 0;

    if(bal < INT32_MIN )
    
        printf("Failed!!!");
    
    else
    
        printf("Success!!!");
    
    return 0;

条件if(bal &lt; INT32_MIN ) 始终为真。怎么可能?

如果我将宏更改为:

#define INT32_MIN        (-2147483648L)

谁能指出问题?

【问题讨论】:

CHAR_BIT * sizeof(int)多少钱? 你试过打印出bal吗? 恕我直言,更有趣的是它 only 对于 -0x80000000,但对于 -0x80000000L-2147483648-2147483648L 是错误的(gcc 4.1.2 ),所以问题是:为什么 int 文字 -0x80000000 与 int 文字 -2147483648 不同? @Bathsheba 我只是在在线编译器上运行程序tutorialspoint.com/codingground.htm 如果你曾经注意到&lt;limits.h&gt; 的(一些化身)将INT_MIN 定义为(-2147483647 - 1),现在你知道为什么了。 【参考方案1】:

0x80000000 是一个 unsigned 字面量,值为 2147483648。

对这个仍然应用一元减号会给你一个非零值的无符号类型。 (事实上​​,对于非零值x,你最终得到的值是UINT_MAX - x + 1。)

【讨论】:

【参考方案2】:

数字常量0x80000000 的类型为unsigned int。如果我们取 -0x80000000 并对其进行 2 秒的补码计算,我们会得到:

~0x80000000 = 0x7FFFFFFF
0x7FFFFFFF + 1 = 0x80000000

所以-0x80000000 == 0x80000000。并且比较(0 &lt; 0x80000000)(因为0x80000000 是无符号的)是正确的。

【讨论】:

这假定为 32 位 ints。尽管这是一个非常常见的选择,但在任何给定的实现中,int 可能更窄或更宽。然而,对于这种情况,这是一个正确的分析。 这与 OP 的代码无关,-0x80000000 是无符号算术。 ~0x800000000 是不同的代码。 简单地说,这对我来说似乎是最好和正确的答案。 @毫米。他正在解释如何取二进制补码。这个答案专门解决了负号对数字的作用。 @Octopus 负号是 not 对数字应用 2 的补码(!)虽然这看起来很清楚,但它并没有描述代码 -0x80000000 中发生的事情!事实上,2 的补码与这个问题完全无关。【参考方案3】:

这个整数文字0x80000000 的类型为unsigned int

根据 C 标准(6.4.4.1 整数常量)

5 整数常量的类型是对应的第一个 可以表示其值的列表。

而这个整数常量可以用unsigned int的类型来表示。

所以这个表达式

-0x80000000 具有相同的 unsigned int 类型。此外,它具有相同的价值 0x80000000 在二进制补码表示中,计算方式如下

-0x80000000 = ~0x80000000 + 1 => 0x7FFFFFFF + 1 => 0x80000000

这样写例子会有副作用

int x = INT_MIN;
x = abs( x );

结果将再次为INT_MIN

因此在这种情况下

bal < INT32_MIN

0unsigned0x80000000 根据通常的算术转换规则转换为long long int 类型进行比较。

显然0小于0x80000000

【讨论】:

【参考方案4】:

这很微妙。

程序中的每个整数文字都有一个类型。 6.4.4.1中的表格规定了它的类型:

Suffix      Decimal Constant    Octal or Hexadecimal Constant

none        int                 int
            long int            unsigned int
            long long int       long int
                                unsigned long int
                                long long int
                                unsigned long long int

如果文字数字不能容纳在默认的int 类型中,它将尝试上表中指示的下一个更大的类型。所以对于常规的十进制整数文字,它就像:

试试int 如果不合适,试试long 如果不合适,请尝试long long

虽然十六进制文字的行为不同! 如果文字不能适合 int 这样的有符号类型,它将首先尝试unsigned int,然后再尝试更大的类型。请参阅上表中的差异。

因此,在 32 位系统上,您的文字 0x80000000 的类型为 unsigned int

这意味着您可以在文字上应用一元 - 运算符,而无需调用实现定义的行为,就像溢出有符号整数时那样。相反,您将获得值0x80000000,一个正值。

bal &lt; INT32_MIN 调用通常的算术转换,表达式0x80000000 的结果从unsigned int 提升到long long0x80000000 的值被保留,0 小于 0x80000000,因此是结果。

当您用2147483648L 替换文字时,您使用十进制表示法,因此编译器不会选择unsigned int,而是尝试将其放入long 中。 L 后缀还表示您想要long 如果可能。如果你继续阅读 6.4.4.1 中提到的表格,L 后缀实际上有类似的规则:如果数字不适合所请求的long,它在 32 位的情况下不适合,编译器会给你long long 非常适合。

【讨论】:

"... 用 -2147483648L 替换文字,您明确得到一个 long,它是有符号的。"嗯,在 32 位 long 系统 2147483648L 中,不适合 long,所以它变成 long long然后应用了 - - 或者我想. @A.S.H 因为 int 可以拥有的最大数量是 0x7FFFFFFF。自己试试吧:#include &lt;limits.h&gt; printf("%X\n", INT_MAX); @A.S.H 不要将源代码中整数文字的十六进制表示与有符号数的底层二进制表示相混淆。以源代码编写的文字 0x7FFFFFFF 始终为正数,但您的 int 变量当然可以包含原始二进制数,最高值为 0xFFFFFFFF。 @A.S.H ìnt n = 0x80000000 强制将无符号文字转换为有符号类型。会发生什么取决于您的编译器 - 它是实现定义的行为。在这种情况下,它选择将整个文字显示到int 中,覆盖符号位。在其他系统上,可能无法表示类型并且您调用未定义的行为 - 程序可能会崩溃。如果您执行int n=2147483648;,您将获得完全相同的行为,因此它与十六进制表示法完全无关。 一元 - 如何应用于无符号整数的解释可以扩展一点。我一直假设(尽管幸运的是从未依赖于假设)无符号值将被“提升”为有符号值,或者结果可能是未定义的。 (老实说,这应该是一个编译错误;- 3u 是什么意思?)【参考方案5】:

C 有一条规则,整数文字可能是signedunsigned,这取决于它是否适合signedunsigned(整数提升)。在32-bit 机器上,文字0x80000000 将是unsigned。在 32 位机器上,-0x80000000 的 2 的补码是 0x80000000。因此,bal &lt; INT32_MIN 的比较是在signedunsigned 之间进行比较,并且在根据C 规则比较之前unsigned int 将转换为long long

C11:6.3.1.8/1:

[...] 否则,如果带符号整数类型的操作数的类型可以表示无符号整数类型的操作数的所有值,则将无符号整数类型的操作数转换为带符号整数类型的操作数。

因此,bal &lt; INT32_MIN 始终是 true

【讨论】:

【参考方案6】:

认为- 是数字常量的一部分时会产生混淆。

在下面的代码中0x80000000 是数字常量。它的类型仅以此确定。 - 是在之后应用的,不会改变类型

#define INT32_MIN        (-0x80000000)
long long bal = 0;
if (bal < INT32_MIN )

Raw 朴素的数字常量是正数。

如果是十进制,则分配的类型是第一个将保存它的类型:intlonglong long

如果常量是八进制或十六进制,则获取第一个保存它的类型:intunsignedlongunsigned longlong longunsigned long long

0x80000000,在 OP 的系统上获取unsignedunsigned long 的类型。无论哪种方式,它都是某种无符号类型。

-0x80000000 也是一些非零值并且是一些无符号类型,它大于 0。当代码将其与 long long 进行比较时,不会在 2比较的两边,所以0 &lt; INT32_MIN 是真的。


另一种定义避免了这种奇怪的行为

#define INT32_MIN        (-2147483647 - 1)

让我们在intunsigned 都是48 位的幻想世界里走一走吧。

然后0x80000000 适合int,类型int 也是如此。 -0x80000000 则为负数,打印出来的结果不同。

[回到现实]

由于0x80000000 在有符号类型之前适合某些无符号类型,因为它刚好比some_signed_MAX 大,但在some_unsigned_MAX 之内,它是一些无符号类型。

【讨论】:

以上是关于为啥 0 < -0x80000000?的主要内容,如果未能解决你的问题,请参考以下文章

为啥像 (0 < a < 5) 这样的条件总是正确的?

为啥 1 // 0.1 == 9.0? [复制]

为啥 `j = +i + ( i < 0 ? len : 0 )` 中的前导 `+` (取自 jQuery 源代码)[重复]

为啥(n = 0)== 0?

为啥进不了for循环,得到的结果总是0? 求大神指教啊!!

为啥我应该在循环中使用 foreach 而不是 for (int i=0; i<length; i++) ?