有符号整数表达式和无符号整数表达式与0x80000000的比较

5
我有以下代码:

#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进行编译。

1
“但为什么编译器会假设我想要一个正数”——因为你没有使用减号?! - Konrad Rudolph
我刚刚测试了一下: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] if(a == -0x80000000) 所以它仍然在抱怨。 - Adam
1
嗯,字面值太大了,无法适应signed int,所以编译器将其变为unsigned - Konrad Rudolph
等号右边的类型不由左边的类型决定。 - Jonathan Potter
3个回答

6

0x80000000是一个无符号整数,因为这个值太大了,不能适应一个int,并且您没有添加任何L来指定它是long类型。

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

我经常使用的一个例子来展示问题的微妙之处

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

这段代码看起来很好,但存在一个bug。如果pts向量为空,则pts.size()0,但是在这里出现了惊人的部分,pts.size()-1是一个巨大的无意义数字(今天通常为4294967295,但取决于平台),循环将使用无效索引(具有未定义的行为)。

将变量更改为size_t i会消除警告,但不会帮助解决相同的bug...

问题的核心是,对于无符号值,a < b-1a+1 < b并不是相同的,即使对于像零这样非常常见的值也是如此;这就是为什么将无符号类型用于非负值(如容器大小)是一个错误的想法和bug来源。

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

即使你知道特定硬件平台上会发生什么,编译器/优化器也可以假设有符号整数溢出永远不会发生:例如,当a是一个常规的int时,像a < a+1这样的测试可能被C++编译器认为总是为真。


你有size_t是无符号的错误引用吗?还是这只是个人观点? - Support Ukraine
1
@StillLearning:当然,我有一个强烈的观点(基于简单逻辑的推理而成),认为这是一个错误。我不是唯一这样认为的(请参见https://dev59.com/iWkw5IYBdhLWcg3wDWTL)... unsigned 不是“非负整数”,而更像是位掩码。容器大小是无符号的,不是因为它很合乎逻辑(它并不是),而是因为历史原因,追溯到普通CPU是16位的时代(在我看来,即使在那个时候也是一个错误)。 - 6502
你提供了一个很好的答案,但我不喜欢将个人观点放在中间。这会对新手产生误导。请删除那部分内容。如果你不能证明它被接受为错误,那么你的答案是不合适的。 - Support Ukraine
@StillLearning:我把“错误地”改成了“历史性意外”(即使它实际上是个错误;-))。此外,我还添加了更多关于为什么通常使用“unsigned”表示数量是不好的解释。 - 6502
我认为这样更好 - 我更喜欢“历史原因”,但仍然比“错误”好。只要明确说明是个人意见,我不介意。我把我的踩转成了赞。 :-) - Support Ukraine

2
看起来你混淆了两个不同的问题:某物的编码和某物的含义。这里举个例子:你看到一个数字97,这是一个十进制编码。但这个数字的含义完全不同。它可以表示ASCII的字符'a',非常高的温度,三角形中的几何角等等。你不能从编码中推断出含义。必须有人向你提供上下文(如ASCII映射、温度等)。
回到你的问题:0x80000000是编码。而INT_MIN是含义。它们不能互换,也不能比较。在特定硬件上,在某些情况下,它们可能相等,就像在ASCII上下文中的97和'a'一样。
编译器警告您有歧义的是含义,而不是编码。给特定编码赋予含义的一种方法是使用强制转换运算符。例如:(unsigned short)-17(student*)ptr; 在32位系统或兼容性的64位系统上,intunsigned 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;
< p >"< code >'b'"的结果是什么?答案会让你震惊:它是未定义的。一些编译器(通常是Microsoft Visual Studio)将编译一个程序,其中b将得到< code >true,而在Android NDK编译器上,b将得到< code >false。原因是Android NDK将'< code >char'类型视为'< code >unsigned char',而Visual Studio将'< code >char'视为'< code >signed char'。因此,在Android手机上,-7的编码实际上具有249的含义,并且不等于(int)-7的含义。解决这个问题的正确方法是将'a'明确定义为有符号字符:

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

1

默认情况下,0x80000000被视为无符号数。您可以通过以下方式避免警告:

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

在评论后进行编辑:

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

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

@MatteoItalia,工程师分为两种类型——一种是使事物工作的人,另一种是花时间在理论研究上的人。尽管这个定义并不明确,但它仍然在今天的每台计算机上运行。 - Support Ukraine
@MatteoItalia - 目前得票为零(并且还会有负面评价),但这几行答案仍然是唯一解决并解决了OP问题的答案。 - Support Ukraine
是的,为“在我的编译器上工作”徽章感到自豪,这正是最近几个Linux内核漏洞中的想法,其中优化器利用这些技术细节执行技术上正确但反直觉的优化。这就是为什么你没有赞(现在是我的反对票),不管你喜不喜欢,这就是编译器行为的趋势 - 特别是在这种情况下,解决方法很简单:你应该只是转换另一种方式,因为无符号溢出是有定义的。 - Matteo Italia
@MatteoItalia - 请提供两个(常用的)编译器的示例,这些编译器对于此代码会产生不同的结果。 - Support Ukraine

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接