ChatGPT解决这个技术问题 Extra ChatGPT

有符号/无符号比较

我试图了解为什么以下代码不会在指定位置发出警告。

//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 为负数,因此小于任何无符号数。简单的。

E
Erik

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

编辑:参考:

5/9:(表达式)

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

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

否则,如果任一操作数为双精度,则另一个应转换为双精度。

否则,如果任一操作数为浮点数,则另一个应转换为浮点数。

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

然后,如果其中一个操作数是 unsigned long,则另一个操作数应转换为 unsigned long。

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

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

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

4.7/2:(积分转换)

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

EDIT2:MSVC 警告级别

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

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

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


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

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

猜猜这段代码的输出?

#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 < ((int)j))”,它会按照您的预期工作。尽管“if ( i < ((_int64)j) )”会更有意义(假设你不能,_int64 是 int 大小的两倍)。
@Peter“为什么不将 unsgiend 转换为(更大的)签名值?”答案很简单:可能没有更大的有符号值。在 32 位机器上,在 long long 之前的日子里,int 和 long 都是 32 位的,没有更大的了。在比较有符号和无符号时,最早的 C++ 编译器将两者都转换为有符号。由于我忘记了什么原因,C 标准委员会改变了这一点。您最好的解决方案是尽可能避免未签名。
@JamesKanze:我怀疑它还必须与以下事实有关,即有符号溢出的结果是未定义行为,而无符号溢出的结果不是,因此在将大无符号值转换为负符号时定义了负符号值到无符号的转换值不是。
@James编译器总是可以生成可以实现这种比较的更直观语义的程序集,而无需转换为更大的类型。在此特定示例中,首先检查是否为 i<0 就足够了。那么 i 肯定小于 j。如果 i 不小于零,则 ì 可以安全地转换为无符号以与 j 进行比较。当然,有符号和无符号之间的比较会更慢,但它们的结果在某种意义上会更正确。
@Nawaz您的底线结论是不正确的,不幸的是:如果签名类型可以包含无符号类型,则无符号将转换为有符号类型,而不是相反。只需在您的示例中将“int i”替换为“long i”即可。它将打印“i 小于 j”。 Erik 的回答详尽地解释了这个棘手的规则。
Y
Yochai Timmer

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

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

4 位示例:

1111 = 15 ?或 -1 ?

所以如果你有 1111 < 0001 ...这是模棱两可的...

但是如果你有 1111 == 1111 ......虽然你不是故意的,但这是同一件事。


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

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

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


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

有问题的代码行不会生成 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

h
hdnn

从 C++20 开始,我们有用于正确比较有符号无符号值的特殊函数 https://en.cppreference.com/w/cpp/utility/intcmp