C++ 通常的算术转换:cpp.sh 与 Godbolt 不同的结果 - 谁错了?

c++ usual arithmetic conversions: cpp.sh vs godbolt different results - who is wrong?

提问人:toxic 提问时间:11/13/2023 最后编辑:Vlad from Moscowtoxic 更新时间:11/19/2023 访问量:172

问:

以下是标准算术转换的简单展示:

// Example program
#include <iostream>

using namespace std;

int main()
{   
    unsigned long a = 0;
    int b = 1;
    long long c = -1;
    
    cout << (c < a*b) << endl;
}

Godbolt 返回 0,cpp.sh 返回 1。

标准(https://en.cppreference.com/w/cpp/language/usual_arithmetic_conversions) 说:

第 4 阶段 ...

  • 如果U的整数转换秩大于或等于S的整数转换秩,则C为U。
  • 否则,如果 S 可以表示 U 的所有值,则 C 为 S。
  • 否则,C 是对应于 S 的无符号整数类型。

哪个编译器是错误的为什么

C++ 类型 表达式 隐式转换

评论

2赞 Konrad Rudolph 11/13/2023
您可以通过完全删除 来简化示例,不需要显示不同的无符号比较行为。b
0赞 toxic 11/13/2023
很好,谢谢,但是,您能否澄清并解释为什么 2 个编译器会产生不同的结果,但标准显然指定了需要发生的情况。
1赞 Mike Vine 11/13/2023
cpp.sh 是一个 32 位编译器。这可能解释了这种差异。
0赞 toxic 11/13/2023
但标准就是标准。它涵盖了 32 位和 64 位架构。符合标准应提供独立于体系结构的相同功能(结果)。
2赞 phuclv 11/13/2023
@MikeVine大小取决于实现,而不是位数。16 位编译器可以毫无问题地具有 128 位 int。或者实际上,64 位 MSVC 使用 32 位长,但 64 位 *nix 使用 64 位长

答:

9赞 Vlad from Moscow 11/13/2023 #1

结果取决于是否等于 。也就是说,该类型是否可以表示该类型的所有正值。sizeof( unsigned long )sizeof( long long )long longunsigned long

如果它不能表示该类型的值,则该表达式的操作数的公共类型unsigned long

(c < a*b)

将由于通常的算术转换,结果表达式产生值,因为转换为类型的非常大的正值。unsigned long longfalse-1unsigned long long

否则,如果表达式的操作数的公共类型是 因为如上所述小于并且该类型可以表示该类型的所有正值,则表达式将产生值 。long longsizeof( unsigned long )sizeof( long long )long longunsigned longtrue

尝试输出表达式和所用系统的值。sizeof( unsigned long )sizeof( long long )

此外,您还可以运行以下测试程序。

#include <iostream>
#include <type_traits>

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

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

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

例如,使用 MS VS C++,得到的结果是

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

在 Godbolt 上使用 clang,得到的结果是

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

来自 C++17 标准(8 个表达式)

11 许多二进制运算符需要算术或 枚举类型导致转换和产生类似结果类型 道路。目的是产生一个通用类型,这也是 结果。这种模式称为通常的算术转换, 其定义如下:

...

(11.5.4) — 否则,如果带有符号整数的操作数类型 type 可以表示操作数类型的所有值,其中 无符号整数类型,无符号整数类型的操作数应为 转换为具有有符号整数类型的操作数类型。

(11.5.5) — 否则,两个操作数都应转换为 与操作数类型相对应的无符号整数类型,其中 有符号整数类型。

在 MS VS C++ 中,对于类型的对象,并且由于类型具有相等的大小,因此可能会出现类似的问题。longunsigned int

请注意,在 C++ 和 C 中,标准整数类型的大小都不是固定的,除了类型,其大小根据定义是相等的。charsigned charunsigned char1

为了在程序中获得预期的结果,您可以编写例如

std::cout << (c < static_cast<long long>( a*b ) ) << std::endl;

或者,如果您的编译器支持 C++20,则可以使用 header 中声明的标准函数。下面是一个演示程序:std::cmp_less<utility>

#include <iostream>
#include <utility>

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

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

    std::cout << std::boolalpha;
    std::cout << "( x < y ) is " << ( x < y ) << '\n';
    std::cout << "std::cmp_less( x, y ) is " << std::cmp_less( x, y ) << '\n';
}

程序输出可能如下所示

sizeof( long long ) = 8
sizeof( unsigned long ) = 8
( x < y ) is false
std::cmp_less( x, y ) is true