【问题标题】:comparison between signed and unsigned integer expressions and 0x80000000有符号和无符号整数表达式与 0x80000000 之间的比较
【发布时间】:2015-09-14 08:05:41
【问题描述】:

我有以下代码:

#include <iostream>

using namespace std;

int main()
{
    int a = 0x80000000;
    if(a == 0x80000000)
        a = 42;
    cout << "Hello World! :: " << a << endl;
    return 0;
}

输出是

Hello World! :: 42

所以比较有效。但是编译器告诉我

g++ -c -pipe -g -Wall -W -fPIE  -I../untitled -I. -I../bin/Qt/5.4/gcc_64/mkspecs/linux-g++ -o main.o ../untitled/main.cpp
../untitled/main.cpp: In function 'int main()':
../untitled/main.cpp:8:13: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
     if(a == 0x80000000)
             ^

所以问题是:为什么 0x80000000 是一个无符号整数?我可以让它以某种方式签名以消除警告吗?

据我了解,0x80000000 将是 INT_MIN,因为它超出了正整数的范围。但是为什么编译器会假设我想要一个正数?

我在 linux 上使用 gcc 版本 4.8.1 20130909 进行编译。

【问题讨论】:

  • “但是为什么编译器会假设我想要一个正数”——因为你没有使用减号?!
  • 我刚刚测试过:warning: comparison between signed and unsigned integer expressions [-Wsign-compare] if(a == -0x80000000) 所以它还在抱怨
  • 嗯,是的,字面量太大,无法放入signed int,因此编译器将其设为unsigned。
  • 等号右边的类型不是由左边的类型决定的。

标签: c++


【解决方案1】:

0x80000000 是一个无符号整数,因为该值太大而无法放入 int 中,并且您没有添加任何 L 来指定它是一个长整数。

发出警告是因为 C/C++ 中的 unsigned 具有非常奇怪的语义,因此通过混淆有符号和无符号整数很容易在代码中出错。这种混合通常是错误的来源,特别是因为标准库出于历史的意外选择使用无符号值来表示容器的大小 (size_t)。

我经常用一个例子来说明问题考虑得多么微妙

// Draw connecting lines between the dots
for (int i=0; i<pts.size()-1; i++) {
    draw_line(pts[i], pts[i+1]);
}

这段代码看起来不错,但有一个错误。如果pts 向量是空的,pts.size() 是0,但令人惊讶的是,pts.size()-1 是一个巨大的无意义数字(今天通常是 4294967295,但取决于平台)并且循环将使用无效索引(具有未定义的行为)。

在此处将变量更改为size_t i 将删除警告,但不会有帮助,因为仍然存在相同的错误...

问题的核心在于,对于无符号值a &lt; b-1 和a+1 &lt; b 即使对于非常常用的值(例如零)也不是一回事;这就是为什么将无符号类型用于非负值(如容器大小)是一个坏主意并且是错误的来源。

另请注意,您的代码在该值不适合整数的平台上是不正确的可移植 C++,因为溢出的行为是为 unsigned 类型定义的,但不是为常规整数定义的。依赖于整数超过限制时发生的情况的 C++ 代码具有未定义的行为。

即使您知道在特定硬件平台上会发生什么,请注意编译器/优化器可以假设有符号整数溢出永远不会发生:例如像a &lt; a+1 这样的测试,其中a 是常规int 可以被 C++ 编译器认为总是正确的。

【讨论】:

  • 你有size_t被误签名的参考还是个人意见?
  • @StillLearning:当然我有一个强烈的观点(基于简单逻辑的推理成熟)这是一个错误。我不是一个人这么想(见stackoverflow.com/q/10168079/320726)...unsigned 不是“非负整数”,但它更像是一个位掩码。容器大小是无符号的,不是因为它是合乎逻辑的(不是),而是因为可以追溯到普通 CPU 为 16 位时的历史事故(顺便说一句,IMO 甚至在那时也是一个错误)。
  • 您提供了一个很好的答案,但我不喜欢将您的个人意见放在中间。这对新人来说是一种误导。请删除该部分。如果你不能证明它被认为是错误的,那么你的答案是不合适的。
  • @StillLearning:我将by mistake 更改为by historical accident(即使它实际上是一个错误;-))。还添加了更多解释为什么使用unsigned 表示数量通常是一个坏主意。
  • 我认为这更好 - 更喜欢historical reasons,但它仍然比mistake 更好。我不介意个人意见,只要明确说明即可。将我的反对票改为赞成票。 :-)
【解决方案2】:

您似乎混淆了 2 个不同的问题:某事物的编码 和 某事物的含义。这是一个示例:您看到一个数字 97。这是一个十进制编码。但是这个数字的含义是完全不同的。它可以表示 ASCII 'a' 字符、非常热的温度、三角形中的几何角度等。您无法从编码中推断出含义。必须有人向您提供上下文(例如 ASCII 地图、温度等)。

回到你的问题:0x80000000 是编码。而INT_MIN 是意义。没有可互换性,也没有可比性。在某些上下文中的特定硬件上,它们可能相等,就像 97 和 'a' 在 ASCII 上下文中相等。

编译器会警告您含义中的歧义,而不是编码中的歧义。赋予特定编码意义的一种方法是强制转换运算符。喜欢(unsigned short)-17 或(student*)ptr;

在 32 位系统或具有向后兼容性的 64 位系统上,int 和 unsigned int 具有 32 位编码,就像在 0x80000000 中一样,但在 64 位系统上,MIN_INT 不等于这个数字。

无论如何 - 您的问题的答案:为了删除警告,您必须为比较的左右表达式提供相同的上下文。 您可以通过多种方式做到这一点。例如:

(unsigned int)a == (unsigned int)0x80000000 或(__int64)a == (__int64)0x80000000 甚至是疯狂的(char *)a == (char *)0x80000000 或任何其他方式只要你保持以下规则:

  1. 您不要降级编码(不要减少它所需的位数)。就像(char)a == (char)0x80000000 不正确,因为您将 32 位降级为 8 位
  2. 您必须为 == 运算符的左侧和右侧提供相同的上下文。就像(char *)a == (unsigned short)0x80000000 不正确,会产生错误/警告。

我想再举一个例子,说明编码和意义之间的区别是多么重要。看代码

char a = -7;  
bool b = (a==-7) ? true : false;

'b' 的结果是什么?答案会让你震惊:它是未定义的。 一些编译器(通常是 Microsoft Visual Studio)将编译一个程序 b 将得到 true 而在 Android NDK 编译器上 b 将得到 false。 原因是Android NDK将'char'类型视为'unsigned char',而Visual Studio将'char'视为'signed char'。所以在Android手机上-7的编码实际上是249的意思,并不等于(int)-7的意思。 解决此问题的正确方法是专门将 'a' 定义为有符号字符:

 signed char a = -7;  
 bool b = (a==-7) ? true : false;

【讨论】:

    【解决方案3】:

    0x80000000 默认被认为是无符号的。 您可以避免这样的警告:

        if (a == (int)0x80000000)
            a=42;
    

    评论后编辑:

    另一种(也许更好)的方式是

        if ((unsigned)a == 0x80000000)
            a=42;
    

    【讨论】:

    • @MatteoItalia 有两种类型的工程师 - 让事情顺利进行的工程师和将时间花在实际工作上的工程师。做我的客人 - 将其定义为未定义 - 今天它仍然适用于每台计算机。
    • @MatteoItalia - 目前的投票为零(以及即将投下的反对票),这几行答案仍然是解决和解决这两个 OP 问题的唯一答案。
    • 是的,为“在我的编译器上工作”徽章感到自豪,这正是他们在最近的几个 Linux 内核错误中的想法,优化器利用这些技术来执行技术上正确但违反直觉的优化。这就是为什么你的赞成票为零(现在是我的反对票),不管你喜欢与否,这是编译器行为的趋势——尤其是在这种解决方法很简单的情况下:你应该只转换另一种方式,因为定义了无符号溢出。
    • @MatteoItalia - 请提供两个(常用)编译器的示例,它们会为此代码提供不同的结果。
    猜你喜欢
    • 2014-10-15
    • 1970-01-01
    • 2013-11-11
    • 2011-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多