【问题标题】:C++ Standard: Strange signed/unsigned arithmetic division behavior for 32 and 64 bitsC++ 标准:32 位和 64 位的奇怪有符号/无符号算术除法行为
【发布时间】:2020-01-05 07:32:04
【问题描述】:

我的代码出现错误行为。调查它让我找到一个显示问题的简短示例:

//g++  5.4.0

#include <iostream>
#include <vector>

int main()
{
    std::vector<short> v(20);

    auto D = &v[5] - &v[10];   
    auto C = D / sizeof(short);

    std::cout << "C = " << C;

}

这个例子很常见。打印出来的结果是什么?

C = 9223372036854775805

在这里测试:https://rextester.com/l/cpp_online_compiler_gcc 还针对 Clang C++VS C++C 进行了测试。结果一样。

在与同事讨论时,我被指向了文档https://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversions

它告诉我们:

  • 如果两个操作数都带符号或都无符号,则转换等级较低的操作数将转换为整数转换等级较高的操作数
  • 否则,如果无符号操作数的转换等级大于或等于有符号操作数的转换等级,则将有符号操作数转换为无符号操作数的类型。
  • 否则,如果有符号操作数的类型可以表示无符号操作数的所有值,则将无符号操作数转换为有符号操作数的类型

看来 second 规则在这里起作用。但它不是正确的。

为了确认第二规则,我测试了这样的例子:

//g++  5.4.0
#include <iostream>

int main()
{
            typedef uint32_t u_t; // uint64_t, uint32_t, uint16_t uint8_t
            typedef int32_t i_t;  // int64_t, int32_t, int16_t int8_t

            const u_t B = 2;
            const i_t X = -1;

            const i_t A1 = X * B;
    std::cout << "A1 = X * B = " << A1 << "\n";

            const i_t C = A1 / B;  // signed / unsigned division
    std::cout << "A1 / B = " << C << "\n";
}

使用 u_ti_t 的不同 rank 组合,发现它适用于任何组合,EXCEPT对于 3264 位(int64_t/uint64_tint32_t/uint32_t)。所以 second 规则 DOES NOT 适用于 168 位。

注意:乘法 操作在所有情况下都正常工作。所以这只是除法问题。

SECOND 规则听起来也是错误的:

有符号操作数转换为无符号操作数的类型

signed 不能转换成 unsigned - 它是一个 !! NEGATIVE 值错误!!! 但相反的转换是正确的 - 无符号操作数被转换为有符号操作数的类型

看着这个我可以注意到这是 C++ 标准算术运算中的一个可能的错误。 而不是:

Otherwise, if the unsigned operand's conversion rank is greater or equal to the conversion rank of the signed operand, the signed operand is converted to the unsigned operand's type.

应该是:

Otherwise, if the signed operand's conversion rank is greater or equal to the conversion rank of the unsigned operand, the unsigned operand is converted to the signed operand's type.

在我看来,如果满足 signedunsigned multiplication/division 那么 unsigned 操作数被转换为 signed ,然后它被转换为正确的等级。至少 x86 汇编器 跟在它后面。

请解释一下这里哪里出错了。我希望这篇文章中的第一个测试适用于代替 auto 类型所涉及的任何类型,但现在它不可能并且C++标准 em> 表明这是正确的行为。

很抱歉提出一个奇怪的问题,但我遇到了这个问题。我在 C/C++ 上编码 30 年,但这是我无法清楚解释的第一个问题 - 无论是错误还是预期行为。

【问题讨论】:

  • sizeof(short) 的类型为 std::size_t(无符号)。
  • auto C = -5 / 2UL; 就足够了。

标签: c++ standards division integer-arithmetic


【解决方案1】:

这里有很多东西要细细琢磨……我只讲一点,因为你忘了真正问一个问题

在您的第二个代码 sn-p 中:

const u_t B = 2;
const i_t X = -1;
const i_t A1 = X * B;

你看到 A1 是 -2 并得出结论,在表达式 X * B 中,两个操作数都被提升为有符号整数。这不是真的。

X * B 中,根据标准,两个操作数都被提升为无符号整数,但其结果随后被转换为带有const i_t A1 = ... 影响的有符号整数。

您可以轻松检查:

const u_t B = 2;
const i_t X = -1;
const auto A1 = X * B; // unsigned

你也可以玩decltype(expression)std::is_signed

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

int main()
{
    signed s = 1;
    unsigned u = 1;
    std::cout << std::boolalpha
              << "  signed *   signed is signed? " << std::is_signed_v<decltype(s * s)> << "\n"
              << "  signed * unsigned is signed? " << std::is_signed_v<decltype(s * u)> << "\n"
              << "unsigned *   signed is signed? " << std::is_signed_v<decltype(u * s)> << "\n"
              << "unsigned * unsigned is signed? " << std::is_signed_v<decltype(u * u)> << "\n";
}

/*
  signed *   signed is signed? true
  signed * unsigned is signed? false
unsigned *   signed is signed? false
unsigned * unsigned is signed? false
*/

demo

【讨论】:

  • 尝试使用其他整数类型的示例。而不是有符号 s = 1;无符号 u = 1;尝试``` int16_t s = 1; uint16_t u = 1; ``` 结果将是: ``` 签名 * 签名是签名的?真正的有符号 * 无符号是有符号的吗?真正的无符号 * 有符号是有符号的吗?真正的无符号 * 无符号是有符号的吗? true ``` C++ 标准没有说明这种差异。对于等级 8 和 16,乘法类型是 signed,但对于 32 和 64,它是 unsigned
  • 尝试使用其他整数类型的示例。而不是 ` 有符号的 s = 1;无符号 u = 1; ` 尝试 ` int16_t s = 1; uint16_t u = 1; ` 结果将是: ` 已签名 * 已签名?真正的有符号 * 无符号是有符号的吗?真正的无符号 * 有符号是有符号的吗?真正的无符号 * 无符号是有符号的吗? true ` C++ 标准没有说明这种差异。对于等级 8 和 16,乘法类型实际上是 signed,但对于 32 和 64,它实际上是 unsigned。为什么?
  • 用 16 位试一试:coliru.stacked-crooked.com/a/a09b9395c09b930b 结果对所有人来说都是 true。 32 位和 16 位具有不同的行为。 C++ 标准中有描述吗?
  • @vlad 这就是为什么你应该问一个问题(而且只有一个)
  • 同意,讨论一个问题会简单得多。但是在这篇文章中,我想讨论 C++ 标准中可能存在的一个错误,该标准几乎在每个 C++ 编译器中都得到了广泛实现。我从多个角度看待问题,并尝试将它们全部展示出来。
猜你喜欢
  • 2011-01-17
  • 1970-01-01
  • 1970-01-01
  • 2012-08-30
  • 1970-01-01
  • 2021-06-09
  • 1970-01-01
  • 2018-11-05
  • 1970-01-01
相关资源
最近更新 更多