有符号/无符号比较


85

我试图理解为什么以下代码没有在指定的位置发出警告。

//from limits.h
#define UINT_MAX 0xffffffff /* maximum unsigned int value */
#define INT_MAX  2147483647 /* maximum (signed) int value */
            /* = 0x7fffffff */

int a = INT_MAX;
//_int64 a = INT_MAX; // makes all warnings go away
unsigned int b = UINT_MAX;
bool c = false;

if(a < b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a > b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a <= b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a >= b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a == b) // no warning <--- warning expected here
    c = true;
if(((unsigned int)a) == b) // no warning (as expected)
    c = true;
if(a == ((int)b)) // no warning (as expected)
    c = true;

我以为这与背景宣传有关,但最后两个似乎不是这样。

在我看来,第一个==比较与其他比较一样是有符号/无符号的不匹配?


3
gcc 4.4.2在使用“ -Wall”调用时会显示警告
波巴2011年

这是推测,但也许可以优化所有比较,因为它在编译时知道答案。
空值

2
啊! 回覆。bobah的评论:我打开了所有警告,现在出现缺少的警告。我认为它应该以与其他比较相同的警告级别设置出现。
彼得

1
@bobah:我真的很讨厌gcc 4.4.2会打印警告(没有办法告诉它只为不平等而打印),因为所有消除警告的方式都会使情况变得更糟。默认提升将-1或〜0可靠地转换为任何无符号类型的最大可能值,但是如果您自己强制转换警告以使其静音,则必须知道确切的类型。因此,如果您更改类型(将其扩展为unsigned long long),那么您与裸露的比较-1仍然可以工作(但会发出警告),而与-1u或的比较(unsigned)-1都会失败。
Jan Hudec

我不知道为什么需要警告,以及为什么编译器无法使其工作。-1为负数,因此小于任何无符号数字。简单。
CashCow 2014年

Answers:


95

当比较带符号和无符号时,编译器会将带符号的值转换为无符号。为了平等,这无关紧要-1 == (unsigned) -1。对于它的事项等进行比较,如符合下列条件:-1 > 2U

编辑:参考:

5/9 :(表达式)

许多期望算术或枚举类型的操作数的二进制运算符都以类似的方式引起转换并产生结果类型。目的是产生一个通用类型,它也是结果的类型。这种模式称为通常的算术转换,其定义如下:

  • 如果一个操作数的类型为long double,则另一个应转换为long double。

  • 否则,如果其中一个操作数为double,则另一个应转换为double。

  • 否则,如果其中一个操作数为float,则另一个应转换为float。

  • 否则,必须在两个操作数上执行积分提升(4.5)(54)。

  • 然后,如果其中一个操作数为无符号长,则另一个应转换为无符号长。

  • 否则,如果一个操作数是long整数,而另一个是unsigned int,则如果long int可以表示unsigned int的所有值,则unsigned int必须转换为long int。否则,两个操作数均应转换为unsigned long int。

  • 否则,如果一个操作数为long,则另一个应转换为long。

  • 否则,如果一个操作数是无符号的,则另一个应被转换为无符号。

4.7 / 2 :(积分转换)

如果目标类型是无符号的,则结果值是与源整数一致的最小无符号整数(模2 n,其中n是用于表示无符号类型的位数)。[注意:在二进制补码表示中,此转换是概念性的,并且位模式没有变化(如果没有截断的话)。]

EDIT2:MSVC警告级别

当然,在MSVC的不同警告级别上要警告的是开发人员的选择。正如我所看到的,他们在有符号/无符号相等与较大/较少比较之间的选择是有意义的,这当然是完全主观的:

-1 == -1的含义与-1 == (unsigned) -1-我发现直观的结果相同。

-1 < 2 并不等同于-1 < (unsigned) 2-乍一看不太直观,IMO应该得到“较早的”警告。


如何将有符号转换为无符号?什么是带符号的值-1的无符号版本?(有符号-1 = 1111,而无符号15 = 1111,按位它们可能相等,但它们在逻辑上不相等。)我知道如果强制执行此转换将起作用,但是编译器为什么会这样做呢?这是不合逻辑的。而且,正如我在上面评论的那样,当我打开警告时,出现了丢失的==警告,这似乎可以支持我所说的内容?
彼得

1
如4.7 / 2所述,有符号为无符号表示二进制补码的位模式没有变化。至于为什么编译器这样做,则C ++标准要求。我相信VS警告在不同级别背后的原因是表达意外的可能性-我同意他们的观点,即有符号/无符号的相等比较比不相等比较“不太可能”成为问题。当然这是主观的-这些是VC编译器开发人员做出的选择。
Erik

好吧,我想我差不多了。我读的是编译器在概念上做的事情:'if((((unsigned _int64)0x7fffffff)==(((unsigned _int64)0xffffffff))'',因为_int64是可同时表示0x7fffffff和0xffffffff的最小类型用未签名的术语?
彼得

2
实际上与相比,(unsigned)-1或者-1u通常比与之相比更差-1。那是因为(unsigned __int64)-1 == -1,但是(unsigned __int64)-1 != (unsigned)-1。因此,如果编译器发出警告,则尝试通过强制转换为无符号或使用来使其静音-1u,如果该值实际上恰好是64位,或者恰好在以后将其更改为一个,则将破坏代码!请记住,size_t只有在64位平台上才使用无符号64位,并且使用-1表示无效值非常常见。
Jan Hudec

1
也许cpmpilers不应该那么做。如果比较带符号和无符号,只需检查带符号的值是否为负。如果是这样,则无论如何都保证小于未签名的那个。
CashCow

32

下例演示了为什么签名/未签名警告很重要,并且程序员必须注意它们。

猜猜这段代码的输出?

#include <iostream>

int main() {
        int i = -1;
        unsigned int j = 1;
        if ( i < j ) 
            std::cout << " i is less than j";
        else
            std::cout << " i is greater than j";

        return 0;
}

输出:

i is greater than j

惊讶吗 在线演示:http : //www.ideone.com/5iCxY

底线:相比而言,如果一个操作数是unsigned,则如果另一个操作数的类型是带符号的,unsigned 则将其隐式转换为!


2
他是对的!这很蠢,但是他是对的。这是我从未遇到过的重大难题。为什么不将未签名的值转换为(更大的)签名值?如果执行“ if(i <((int)j)”,则它会按预期工作。虽然“ if(i <((__int64)j)”更有意义(假设,但您不能知道_int64是int大小的两倍)。
彼得

6
@Peter“为什么不将unsgiend转换为(较大的)有符号值?” 答案很简单:可能没有更大的符号值。在32位机器上,在long long之前的日子里,int和long都是32位,并且没有什么比这更大的了。比较有符号和无符号时,最早的C ++编译器都将它们都转换为有符号。我忘记了什么原因,C标准委员会对此进行了更改。最好的解决方案是尽可能避免未签名。
James Kanze 2011年

5
@JamesKanze:我怀疑它也必须与事实有关,即有符号溢出的结果是未定义行为,而没有无符号溢出的结果不是,因此定义了将负无符号值转换为无符号,而将大无符号值转换为负符号。价值不是
Jan Hudec

2
@James编译器始终可以生成可实现此比较的更直观语义的程序集,而无需转换为更大的类型。在此特定示例中,首先检查是否满足即可i<0。然后ij确定要小。如果i不小于零,则ì可以安全地转换为unsigned以与进行比较j。当然,有符号和无符号之间的比较会比较慢,但是从某种意义上来说,它们的结果会更正确。
2014年

@Sven我同意。该标准可能要求比较才能适用于所有实际值,而不是转换为两种类型之一。但是,这仅适用于比较;我怀疑委员会不希望对比较和其他操作使用不同的规则(也不想攻击实际上不存在要比较的类型时指定比较的问题)。
James Kanze 2014年

4

==运算符只是进行按位比较(通过简单除法以查看它是否为0)。

比比较小的/大的更多地依赖于数字的符号。

4位示例:

1111 = 15?或-1?

因此,如果您的1111 <0001 ...模棱两可...

但是如果您有1111 == 1111 ...这是同一回事,尽管您并非故意如此。


我理解这一点,但无法回答我的问题。如您所指出的,如果符号不匹配,则1111!= 1111。编译器知道这些类型不匹配,那么为什么不警告呢?(我的观点是我的代码可能包含许多这样的不匹配,因此我没有受到警告。)
Peter

这就是它的设计方式。相等性测试检查相似性。和它相似。我同意你的看法,不应该这样。您可以执行宏或将x == y重载为!((x <y)||(x> y))的东西
Yochai Timmer

1

在使用2补码(大多数现代处理器)表示值的系统中,即使是二进制形式,它们也相等。这可能就是为什么编译器不抱怨a == b的原因

对我来说,奇怪的编译器不会在==((int)b)上警告您。我认为它应该给您一个整数截断警告之类的东西。


1
C / C ++的理念是:编译器相信开发人员知道在类型之间进行显式转换时他正在做什么。因此,没有警告(至少默认情况下-如果警告级别设置为高于默认值,我相信会有编译器为此生成警告)。
彼得Török

0

有问题的代码行不会生成C4018警告,因为Microsoft使用了不同的警告编号(即C4389)来处理这种情况,并且默认情况下未启用C4389(即,级别3)。

从C4389的Microsoft文档中:

// C4389.cpp
// compile with: /W4
#pragma warning(default: 4389)

int main()
{
   int a = 9;
   unsigned int b = 10;
   if (a == b)   // C4389
      return 0;
   else
      return 0;
};

其他答案已经很好地解释了为什么Microsoft可能决定对等式运算符进行特殊处理,但是我发现这些答案在没有提及C4389或如何在Visual Studio中启用的情况下并没有多大帮助。

我还应该提到,如果您要启用C4389,则也可以考虑启用C4388。不幸的是,没有C4388的官方文档,但它似乎以如下形式弹出:

int a = 9;
unsigned int b = 10;
bool equal = (a == b); // C4388
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.