Visual C++ 2010:如果我将一个比较数加上“const”,为什么“signed/unsigned mismatch”错误会消失?

13

我有如下简单的C++代码:

#include "stdafx.h"
int main()
{
    int a = -10;
    unsigned int b = 10;
    //  Trivial error is placed here on purpose to trigger a warning.
    if( a < b ) {
        printf( "Error in line above: this will not be printed\n" );
    }
    return 0;
}

使用Visual Studio 2010(默认的C ++控制台应用程序)进行编译,第7行出现“warning C4018:“'<':signed/unsigned mismatch”,并且代码存在逻辑错误。如预期。

但是,如果我将unsigned int b = 10;更改为const unsigned int b = 10;,则警告会消失!是否有任何已知原因导致这种行为?无论const如何,gcc都会显示警告。

更新

从评论中可以看出,很多人认为“它只是被优化了,所以不需要警告”。不幸的是,确实需要警告,因为我的代码示例实际上存在一个精心放置以触发警告的逻辑错误:即使-10实际上小于10print语句也不会被调用。这个错误是众所周知的,“signed/unsigned warning”正是为了找到这样的错误而引发的。

更新

我还可以从评论中看到,很多人在我的代码中“发现”了一个有符号/无符号的逻辑错误,并对其进行了说明。没有必要这样做——这个错误是纯粹为了触发警告而放置的,是微不足道的(-10转换为(unsigned int)-10,即0xFFFFFFFF-10),问题并不在于此 :)。


5
听起来像是编译器的一个bug。它可能会优化掉这个变量,所以你只剩下a < 10 - Apples
3
不,等等,就是这样!一个被初始化为编译时常量表达式的整型const变量...它就是a<10 - Mooing Duck
2
我不敢将其称为错误;比较被按照语言标准要求进行评估。这很可能是逻辑错误(即程序员原本意图不是这样的)。 - Keith Thompson
1
我的调试器显示 FFFFFFF6(-10)不小于 0Ah(10),因此程序跳过了打印代码块。 - StarPilot
1
Make Visual Studio warn for unsigned comparisons中提到了这个bug:该问题似乎只间接与sizeof的使用有关。编译器在涉及无符号类型的常量值比较的其他情况下也无法发出警告。不幸的是,该错误链接已失效。 - Jesse Good
显示剩余14条评论
1个回答

9
这是一个Visual Studio的错误,但让我们先从不是错误的方面开始说起。
适用于当时的C++标准的第5节第9条款首先讨论了如果操作数具有不同的位宽应该做什么,然后再讨论操作数相同时但符号不同应该做什么:

......否则,如果具有无符号整数类型的操作数的等级大于或等于另一个操作数的类型的等级,则带有有符号整数类型的操作数将被转换为具有无符号整数类型的操作数的类型。

这就是我们了解比较必须在无符号算术下进行的地方。现在我们需要了解这对值-10意味着什么。
第4.6节告诉我们:

如果目标类型是无符号的,则结果值是与源整数同余的最小无符号整数(模2n,其中n是用于表示无符号类型的位数)。[注:在二进制补码表示中,此转换是概念性的,并且没有更改位模式(如果没有截断)。 —注] 3如果目标类型为有符号,则该值在可以在目标类型(和位域宽度)中表示时不变;否则,该值是实现定义的。

如您所见,一个特定的非常大的值(4294967286或0xFFFFFFF6,假设unsigned int是32位数字)正在与10进行比较,因此标准保证真的从未调用printf
现在您可以相信我,在这种情况下,没有规则要求发出诊断,因此编译器可以自由选择不发出任何警告。(事实上,有些人写-1以产生全为1的位模式。其他人使用int迭代数组,这导致了size_tint之间的有符号/无符号比较。难看,但保证编译。)
现在Visual Studio“自愿”发出一些警告。
这会导致在默认设置(级别3)下就已经出现警告:
int a = -10;
unsigned int b = 10;
if( a < b ) // C4018
{
    printf( "Error in line above: this will not be printed\n" );
}

以下内容需要使用/W4才能得到警告。请注意,警告已被重新分类。它从一个C4018警告变成了一个C4245警告。这显然是有意的。一个破坏比较的逻辑错误通常比一个在正-正比较中似乎有效但在正-负比较中会崩溃的错误更不危险。
const int a = -10;
unsigned int b = 10;
if( a < b ) // C4245
{
    printf( "Error in line above: this will not be printed\n" );
}

但是您的情况与众不同:
int a = -10;
const unsigned int b = 10;
if( a < b ) // no warning
{
    printf( "Error in line above: this will not be printed\n" );
}

完全没有任何警告。(如果您想确保,请使用-Wall重试。)这是一个错误。微软在此处有所说明:

感谢您提交反馈。这是我们应该发出C4018警告的场景。不幸的是,鉴于我们可用的资源,这个具体问题在下一个版本中不是优先级很高的修复。

出于好奇,我使用Visual Studio 2012 SP1进行了检查,缺陷仍然存在-使用-Wall没有警告。


你可能对那个“-1”的事情是正确的,我想我自己也这样做过。不过使用“~0”会更有意义。 - Mark Ransom
@MarkRansom:~0 的理论问题在于它的类型是 int,其行为和值取决于有符号表示。显然,在实践中,所有东西都使用二进制补码,因此 ~0 == -1。如果您想要可移植地最大化无符号类型(而不重复类型名称或使用 decltype),则需要分配 -1~0U。使用 -1 强调无符号类型是算术模 2^n,而使用 ~0U 则强调无符号类型是位字段。 - Steve Jessop
哎呀,我之前的评论应该使用~0ULL,因为如果目标类型大于unsigned int,那么~0U会失败。如果你愿意依赖于特定的类型,那么有UINT_MAX可用,不需要使用-~。个人而言,在位操作时我使用-1 - Steve Jessop
我已将警告级别从3(默认值)更改为4(接近最大值),但我看不到任何C4245警告 :(。你自己检查了吗? - grigoryvp
1
@EyeofHell - 哎呀 - 这取决于您放置 const 的位置。回答已更新以涵盖两种方式。 - Jirka Hanika

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