将无符号整数与负文字进行比较

Comparing unsigned integer with negative literals

我有这个简单的 C 程序。

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

bool foo (unsigned int a) {
    return (a > -2L);
}

bool bar (unsigned long a) {
    return (a > -2L);
}

int main() {
    printf("foo returned = %d\n", foo(99));
    printf("bar returned = %d\n", bar(99));
    return 0;
}

我运行这个时候的输出-

foo returned = 1
bar returned = 0

在 godbolt 中重新创建 here

我的问题是为什么 foo(99) return 为真而 bar(99) return 为假。

对我来说,bar 会 return 错误是有道理的。为简单起见,我们假设 long 是 8 位,然后(对有符号值使用二进制补码):

99 == 0110 0011
-2 == unsigned 254 == 1111 1110

很明显 CMP 指令会看到 1111 1110 更大并且 return 错误。

但是我不明白 foo 函数在幕后发生了什么。 foo 的程序集似乎总是硬编码为 return mov eax,0x1。我本以为 foo 会做类似于 bar 的事情。这是怎么回事?

在第一个函数中

bool foo (unsigned int a) {
    return (a > -2L);
}

表达式 a > -2L 的两个操作数都具有类型 long(由于通常的算术转换,第一个操作数被转换为类型 long,因为类型的秩long 大于类型 unsigned int 的等级,并且所用系统中类型 unsigned int 的所有值都可以用类型 long 表示)。并且很明显正值99L大于负值-2L.

如果 sizeof( long ) 等于 sizeof( unsigned int ),第一个函数可以产生结果 0。在这种情况下,类型 long 无法表示类型 unsigned int 的所有(正)值。结果,由于通常的算术转换,两个操作数都将转换为 unsigned long.

类型

例如 运行 函数 foo 使用 MS VS 2019 其中 sizeof( long ) 等于 4 因为 sizeof( unsigned int ) 你会得到结果0.

这里有一个用C++编写的演示程序,直观地展示了为什么使用MS VS 2019调用函数foo的结果可以等于0.[=45=的原因]

#include <iostream>
#include <iomanip>
#include <type_traits>

int main()
{
    unsigned int x = 0;
    long y = 0;

    std::cout << "sizeof( unsigned int ) = " << sizeof( unsigned int ) << '\n';
    std::cout << "sizeof( long ) = " << sizeof(long) << '\n';

    std::cout << "std::is_same_v<decltype( x + y ), unsigned long> is "
              << std::boolalpha
              << std::is_same_v<decltype( x + y ), unsigned long>
              << '\n';
}

程序输出为

sizeof( unsigned int ) = 4
sizeof( long ) = 4
std::is_same_v<decltype( x + y ), unsigned long> is true

一般来说,第一个函数的结果是实现定义的。

在第二个函数中

bool bar (unsigned long a) {
    return (a > -2L);
}

两个操作数的类型都是unsigned long(同样由于通常的算术转换和等级unsigned longsigned long彼此相等,所以类型 signed long 转换为类型 unsigned long) 并且 -2L 解释为 unsigned long 大于 99.

我认为这里发生的是编译器的隐式提升。当您对两个不同的基元进行比较时,编译器会将其中一个提升为与另一个相同的类型。我相信规则是使用具有较大可能值的类型作为标准。 因此,在 foo() 中,您隐式地将您的参数提升为带符号的 long 类型,并且比较按预期进行。 在 bar() 中,您的参数是一个无符号长整数,它的最大值比有符号长整数大。这里编译器将 -2L 提升为 unsigned long,变成一个非常大的数字。

这包含在 C 类 中,并在文档中指定。以下是您如何使用文档来解决这个问题。

the 2018 C standard 中,您可以在索引中查找 > 或“关系表达式”,以查看第 68-69 页对它们的讨论。在第 68 页,您会找到第 6.5.8 节,其中涵盖关系运算符,包括 >。阅读它,第 3 段说:

If both of the operands have arithmetic type, the usual arithmetic conversions are performed.

“常用算术转换”列在第 39 页定义的索引中。第 39 页有第 6.3.1.8 条“常用算术转换”。本节解释了将算术类型的操作数转换为通用类型,并给出了确定通用类型的规则。对于两个不同符号的整数类型,例如bar中的unsigned longlong inta-2L),它表示,如果unsigned类型的等级大于或等于其他类型的等级,有符号类型转换为无符号类型。

“排名”不在索引中,但您可以搜索文档找到它在第 6.3.1.1 节中讨论,它告诉您 long int 的排名大于 int,并且任何无符号类型与相应类型具有相同的等级。

现在您可以考虑 bar 中的 a > -2L,其中 aunsigned long。这里我们有一个 unsigned longlong 的比较。它们具有相同的等级,因此 -2L 被转换为 unsigned long。将有符号整数转换为无符号整数在第 6.3.1.3 节中讨论。它说这个值是通过对模 ULONG_MAX+1 进行包装来转换的,因此 signed long −2 会产生一个大整数。然后将值为 99 的 a> 的大整数进行比较,结果为 false,因此返回零。

对于foo,我们继续使用通常的算术转换规则。当无符号类型的秩不大于或等于有符号类型的秩,但有符号类型可以表示无符号类型操作数类型的所有值时,无符号类型操作数转换为操作数签名类型。在foo中,aunsigned int-2Llong int。想必在你的 C 实现中,long int 是 64 位的,所以它可以表示一个 32 位的 unsigned int 的所有值。所以这个规则适用,并且a被转换为long int。这不会更改值。因此 a 的原始值 99 与 −2 与 > 进行比较,结果为真,因此返回 1。

原因与整数转换规则有关。

在第一种情况下,您使用 > 运算符将 unsigned intlong 进行比较,在第二种情况下,您将 unsigned longlong.

必须首先使用通常的算术转换将这些操作数转换为通用类型。这些在 C standard 的第 6.3.1.8p1 节中有详细说明,以下摘录着重于整数转换:

If both operands have the same type, then no further conversion is needed.

Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank.

Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.

Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type.

Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.

在比较 unsigned intlong 的情况下,第二个粗体段落适用。 long 具有更高的等级并且(假设 long 是 64 位并且 int 是 32 位)可以容纳比 unsigned int 可以容纳的所有值,因此 unsigned int 操作数a 转换为 long。由于所讨论的值在 long 范围内,第 6.3.1.3p1 节规定了转换的发生方式:

When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged

所以这个值被保留下来,我们剩下 99 > -2 这是真的。

在比较 unsigned longlong 的情况下,第一个粗体段落适用。两种类型的级别相同,但符号不同,因此 long 常量 -2L 被转换为 unsigned long。 -2 超出了 unsigned long 的范围,因此必须进行值转换。此转换在第 6.3.1.3p2 节中指定:

Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.

所以long值-2将被转换为unsigned long值264-2,假设unsigned long是64位.所以我们剩下 99 > 264-2,这是错误的。