有符号/无符号比较

Posted

技术标签:

【中文标题】有符号/无符号比较【英文标题】:Signed/unsigned comparisons 【发布时间】:2011-07-21 22:41:59 【问题描述】:

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

//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;

我还以为是后台推广的问题,后面两个好像不是这么说的。

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

【问题讨论】:

gcc 4.4.2 在使用 '-Wall' 调用时打印警告 这是推测,但它可能会优化所有比较,因为它在编译时知道答案。 啊!回覆。 bobah 的评论:我打开了所有警告,现在出现了丢失的警告。我认为它应该出现在与其他比较相同的警告级别设置中。 @bobah:我真的很讨厌 gcc 4.4.2 打印那个警告(没有办法告诉它只打印不等式),因为所有消除该警告的方法都会使事情变得更糟 。默认提升可靠地将 -1 或 ~0 转换为任何无符号类型的最高可能值,但如果您通过自己强制转换来消除警告,则您必须知道 exact 类型。因此,如果您更改类型(将其扩展为 unsigned long long),您与裸 -1 的比较仍然有效(但会发出警告),而您与 -1u(unsigned)-1 的比较都将失败。跨度> 我不知道您为什么需要警告,以及为什么编译器无法使其正常工作。 -1 为负数,因此小于任何无符号数。简单。 【参考方案1】:

在比较有符号和无符号时,编译器会将有符号值转换为无符号值。对于平等,这并不重要,-1 == (unsigned) -1。对于其他比较,它很重要,例如以下是正确的:-1 &gt; 2U

编辑:参考:

5/9:(表达式)

许多期望的二元运算符 算术或枚举的操作数 类型原因转换和产量 结果类型以类似的方式。这 目的是产生一个共同的类型, 这也是结果的类型。 这种模式被称为通常 算术转换,它们是 定义如下:

如果有 操作数是 long double 类型,则 其他应转换为长 双倍。
否则,如果任一操作数 是双的,另一个应该是 转换为双倍。
否则,如果 一个操作数是浮点数,另一个 应转换为浮点数。
否则,积分促销 (4.5) 应在两者上执行 操作数.54)
那么,如果任一操作数 无符号长,另一个应为 转换为无符号长整数。
否则,如果一个操作数是长 int 和另一个 unsigned int,然后 如果一个 long int 可以代表所有 unsigned int 的值, unsigned int 应转换为 长整数;否则两个操作数 应转换为无符号长 诠释。
否则,如果任一操作数是 长,另一个应转换为 长。
否则,如果任一操作数 未签名,另一个应为 转换为无符号。

4.7/2:(积分转换)

如果目标类型是无符号的, 结果值最小 无符号整数等于 源整数(模 2n 其中 n 是 用于表示的位数 无符号类型)。 [注:在一个二 补充表示,这 转换是概念性的,有 位模式没有变化(如果有 是没有截断)。 ]

EDIT2:MSVC 警告级别

MSVC 的不同警告级别所警告的内容当然是开发人员做出的选择。在我看来,他们关于有符号/无符号相等与更大/更少比较的选择是有道理的,这当然是完全主观的:

-1 == -1-1 == (unsigned) -1 的含义相同 - 我发现这是一个直观的结果。

-1 &lt; 2 -1 &lt; (unsigned) 2 的意思相同 - 乍一看不太直观,IMO 应该“提前”警告。

【讨论】:

如何将有符号转换为无符号?有符号值 -1 的无符号版本是什么? (有符号的-1 = 1111,而无符号的15 = 1111,按位它们可能相等,但它们在逻辑上不相等。)我知道,如果您强制进行此转换,它将起作用,但是编译器为什么要这样做?这是不合逻辑的。此外,正如我上面评论的那样,当我打开警告时,出现了缺少的 == 警告,这似乎支持了我所说的话? 正如 4.7/2 所说,有符号到无符号意味着二进制补码的位模式没有变化。至于编译器为什么这样做,这是C++标准要求的。我相信 VS 在不同级别发出警告背后的原因是表达式可能是无意的——我同意他们的观点,即有符号/无符号的相等比较比不等比较“不太可能”成为问题。这当然是主观的 - 这些是 VC 编译器开发人员做出的选择。 好吧,我想我差不多明白了。我读到的方式是编译器(从概念上)在做:'if(((unsigned _int64)0x7fffffff) == ((unsigned _int64)0xffffffff))',因为_int64是可以表示0x7fffffff和0xffffffff的最小类型以未签名的形式? 实际上与(unsigned)-1-1u 相比,通常比与-1 相比。那是因为(unsigned __int64)-1 == -1,但(unsigned __int64)-1 != (unsigned)-1。因此,如果编译器发出警告,您尝试通过强制转换为无符号或使用 -1u 来使其静音,如果该值实际上是 64 位,或者您后来碰巧将其更改为 64 位,那么您将破坏您的代码!请记住,size_t 是无符号的,仅在 64 位平台上为 64 位,并且使用 -1 表示无效值非常常见。 也许 cpmpilers 不应该那样做。如果它比较有符号和无符号,只需检查有符号值是否为负。如果是这样,则无论如何都保证小于未签名的。【参考方案2】:

为什么有符号/无符号警告很重要,程序员必须注意它们,下面的例子说明了这一点。

猜猜这段代码的输出?

#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 如果它的类型是有符号的!

【讨论】:

他是对的!这很愚蠢,但他是对的。这是我以前从未遇到过的一个重大问题。为什么它不将无符号转换为(更大的)有符号值?!如果您执行“if (i @Peter “为什么不将 unsgiend 转换为(更大的)有符号值?”答案很简单:可能没有更大的有符号值。在 32 位机器上,在 long long 的前几天,int 和 long 都是 32 位,并且没有更大的东西。在比较有符号和无符号时,最早的 C++ 编译器将两者都转换为有符号。由于我忘记了什么原因,C 标准委员会改变了这一点。您最好的解决方案是尽可能避免未签名。 @JamesKanze:我怀疑这也与以下事实有关,即有符号溢出的结果是未定义的行为,而无符号溢出的结果不是,因此定义了负符号值到无符号的转换,而将大的无符号值转换为负符号值是不是 @James 编译器始终可以生成程序集,该程序集将实现这种比较的更直观语义,而无需转换为更大的类型。在这个特定的例子中,首先检查是否i&lt;0 就足够了。那么i 肯定小于j。如果i 不小于零,则可以安全地将ì 转换为无符号以与j 进行比较。当然,有符号和无符号之间的比较会慢一些,但它们的结果在某种意义上会更正确。 @Nawaz 你的底线结论是不正确的,不幸的是:如果有符号类型可以包含无符号类型,无符号将被转换为有符号类型,而不是相反。只需在您的示例中将“int i”替换为“long i”即可。它将打印“i 小于 j”。 Erik 的回答详尽地解释了这个棘手的规则。【参考方案3】:

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

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

4 位示例:

1111 = 15 ?还是-1?

所以如果你有 1111

但如果你有 1111 == 1111 ...虽然你不是故意的,但它是一样的。

【讨论】:

我明白这一点,但它没有回答我的问题。正如您所指出的,如果符号不匹配,则为 1111 != 1111。编译器知道类型不匹配,那么为什么不发出警告呢? (我的意思是我的代码可能包含许多我没有被警告过的不匹配。) 这就是它的设计方式。相等性测试检查相似性。它是相似的。我同意你的看法,不应该这样。你可以做一个宏或重载 x==y 的东西 !((xy))【参考方案4】:

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

对我来说,奇怪的是编译器并没有在 a == ((int)b) 上警告你。我认为它应该给你一个整数截断警告之类的。

【讨论】:

C/C++ 的哲学是:编译器相信开发人员在显式转换类型时知道他在做什么。因此,没有警告(至少默认情况下 - 如果警告级别设置为高于默认值,我相信有编译器会为此生成警告)。【参考方案5】:

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

来自 C4389 的 Microsoft docs:

// 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;
;

其他答案已经很好地解释了为什么微软可能决定用相等运算符制作一个特殊情况,但我发现如果不提及 C4389 或 how to enable it in Visual Studio,这些答案并不是很有帮助。

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

int a = 9;
unsigned int b = 10;
bool equal = (a == b); // C4388

【讨论】:

以上是关于有符号/无符号比较的主要内容,如果未能解决你的问题,请参考以下文章

如何忽略“有符号和无符号整数表达式之间的比较”?

有符号/无符号比较给出了意想不到的结果[重复]

有符号和无符号字符之间的比较

关于:有符号与无符号整数的大小比较

为啥 C 没有无符号浮点数?

g++ 警告:无符号表达式 < 0 的比较始终为假