【问题标题】:Is subtracting larger unsigned value from smaller in C++ undefined behaviour?是否从 C++ 未定义行为中的较小值中减去较大的无符号值?
【发布时间】:2015-02-28 08:42:49
【问题描述】:

在下面的程序中,在“积分提升”和“通常的算术转换”之后,运算符- 的两个操作数仍然是unsigned long long。后来,C++11 标准说:

5.7.3 二元-运算符的结果是第二个操作数与第一个操作数相减所得的差。

该标准是否更详细地定义了减法的执行方式(或参考其他定义减法的文档)?

从较小的无符号整数中减去较大的无符号整数是否会产生未定义的行为,为什么?

在下面的示例程序中执行分配 c=a-b 是否保证 c 在所有(甚至理论上)符合 C++11 标准的可能机器架构上都是 -3,为什么?

int main()
{
    unsigned long long a=2, b=5;
    signed long long c=a-b;
}

【问题讨论】:

  • 减法在无符号算术中完成,然后才转换为有符号。
  • 无符号算术总是环绕 (mod 2^n)

标签: c++ unsigned signed subtraction


【解决方案1】:

无符号值的减法由 (3.9.1) [basic.fundamental]/4 明确定义:

声明为unsigned 的无符号整数应遵守算术模2 的规律n 其中n 是该特定整数大小的值表示。46

46) 这意味着无符号算术不会溢出,因为无法由生成的无符号整数类型表示的结果以比可以表示的最大值大一的数字为模减少通过生成的无符号整数类型。

但是,赋值导致c 具有实现定义的值(也就是说,您的里程可能会有所不同)。关于赋值运算符,(5.17) [expr.ass]/3 不得不说

如果左操作数不是类类型,则表达式被隐式转换(第 4 条)为左操作数的 cv 非限定类型。

第 4 条 ([conv]) 在 (4.7) [conv.integral]/3 中说

如果目标类型是有符号的,如果它可以在目标类型(和位域宽度)中表示,则值不变; 否则,该值由实现定义。

重申一下:a - b 是明确定义的,c = a - b 不是因为a - b 的结果不能由c 表示。

造成这种情况的历史原因是,虽然今天几乎所有计算机都使用二进制补码表示有符号整数,但在过去,有些机器使用 other representations(尤其是二进制补码和有符号量值)并没有相同的值取值范围为二进制补码。如果用二进制补码表示的自然术语定义无符号到有符号的转换,那么 C++ 将不可能(或至少非常难以)在此类机器上实现,并且如果用其中一种表示的自然术语定义它,我们今天会有更大的问题。

【讨论】:

  • signed long long c = static_cast<signed long long>(a)-static_cast<signed long long>(b); 是否被明确定义并保证 a 和 b 的所有值等于或低于 std::numeric_limits<signed long long>::max() 时 c == -5?
  • c == -3,但是对于给定的值是肯定的。 a - b 和有符号的ab 是用-3 的值很好定义的,c 可以表示该值。但是,ab 的值组合会溢出或下溢,并且该值再次由实现定义。
  • @Wintermute:如果减法的结果上溢或下溢,实际上在减法之前强制转换为有符号会导致 undefined,而不是实现定义的行为。正如您从规范中引用的那样,在减去无符号值后转换为有符号是实现定义的。
  • @Nemo 是的,如果减法溢出或下溢,这是未定义的行为。我正在编辑相关段落。
  • 哦,等等——脑子放个屁。它实际上是 OP 要求的明确定义的方式。如果ab 都在0(因为它们是无符号的)和std::numeric_limits<signed long long>::max() 之间,则a - bab 的结果是可以用@987654345 表示的数字@。所以没关系。中止不相关的编辑(即将引用 (5) [expr]/4)。
【解决方案2】:

如果我没记错的话,a - b 的结果是明确定义的,因为 C++ 保证了它们的补码语义。但是,产生的 underfklow 将超出 signed long long 的范围,因此将此值转换为带符号的 long long 将是未定义的行为。

【讨论】:

  • unsigned 算术的二进制补码??
  • 这是错误的两次......首先,正如@OliverCharlesworth 所说,二进制补码只是有符号值的一个有意义的概念。其次,将超出范围的无符号值强制转换为有符号不是整数溢出,并且只是实现定义(不是未定义)的行为。
猜你喜欢
  • 2012-03-20
  • 1970-01-01
  • 1970-01-01
  • 2012-09-30
  • 2021-11-25
  • 1970-01-01
  • 2021-05-04
  • 1970-01-01
  • 2017-12-17
相关资源
最近更新 更多