为什么C++的下溢/上溢行为被认为是未定义的行为?

8
我知道整数下溢和上溢是未定义的。
然而,考虑到C++最终编译成汇编语言,行为实际上是被定义的吗?
位表示保持不变,整数格式仍然是0111..11总会翻转到1000..00,下溢也是一样,那么为什么它不被认为是定义好的行为呢?
关于汇编编译,我从我们在学校学到的基础汇编中推导出来,但代码块能够给出更多信息。
int x = INT_MAX;
int y = x+1;

编译成
00401326    movl   $0x7fffffff,0x8(%esp)
0040132E    mov    0x8(%esp),%eax
00401332    inc    %eax
00401333    mov    %eax,0xc(%esp)

现在,无论x的值是多少,都会有一个inc或add指令,那么未定义行为从哪里产生?


这个问题很有趣,但我认为你应该添加两种情况的C++编码示例,并且展示编译器为每种情况生成的反汇编代码。 - barak manos
我必须同意@barakmanos的观点。 - MZaragoza
在C++中未定义此项,因为世界上的各种CPU无法达成一致的定义。例如,某些CPU使用“饱和”数学,其中溢出导致最大值。 - brian beuning
它是针对特定的硬件执行定义的,但不是由C++标准定义的。 - user207421
如果编译器可以假设某些行为是未定义的,那么它肯定有其原因。在大多数情况下,这是出于性能考虑。编译器可以做出更多的假设,从而产生更优化的代码。但是你仍然可以对无符号整数执行“标准”操作。 - DawidPi
3个回答

7
然而,考虑到C++最终编译成汇编语言,行为是否实际上已经被定义了呢? 不是的,因为编译器决定它生成什么样的汇编代码。如果编译器希望,它可以生成在遇到未定义行为时擦除硬盘的汇编代码。(实际上,“C++最终编译成汇编语言”甚至可能不是真的。例如,存在C ++解释器 - 标准没有指定C++应该如何编译/转化成什么格式。)标准之所以决定将其留空是出于优化的机会。如果有符号溢出是未定义的,那么编译器可以假设x + 1 > x始终为真,并生成依赖于这个前提条件的更简单/更短/更快的代码。

5

C++标准中未定义有符号整数的溢出,这是因为不同的编译器、汇编器和平台可能会以不同的方式解释它们。

当你知道程序将在哪个平台上运行时,你可以推理出程序的行为,但如果没有这方面的知识,就无法预测它将如何行动。

按位表示保持不变,整数格式仍然相同

这并不一定是真的。


谢谢,你能分享一种情况,其中位表示法不会导致intmax + 1 = intmin,以及intmin-1 = intmax的情况吗? - user87166
2
@user87166 当编译器将其优化掉,因为它总是UB时。 - user1804599
4
@user87166 http://en.wikipedia.org/wiki/Signed_number_representations#Comparison_table - KoKuToru
如果原因是平台依赖性,为什么无符号溢出被定义得很好? - Karoly Horvath
@sjdowling 这是因为它是有保证的 (https://dev59.com/Q37aa4cB1Zd3GeqPoEUT#22801135) (unsigned int) -1 == std::numeric_limits<unsigned int>::max() 或者你指的是其他什么? - Tim Seguine
显示剩余2条评论

0

据我所知,这是未定义的原因是因为C++不规定目标机器如何存储数字。

假设每个字节/ char 为8位。这将给我们:

  • std::numeric_limits<char>::max()
    • 2的补码:127(0b01111111)
    • 1的补码:127(0b01111111)
    • 有符号数:127(0b01111111)
  • std::numeric_limits<char>::min()
    • 2的补码:-128(0b10000000)
    • 1的补码:-127(0b10000000)
    • 有符号数:-127(0b11111111)

您已经可以看到,对于最小值,我们具有不同的位模式和最小值,而最大值相同。

那么,如果将1添加到最大值会发生什么?假设我们强制转换为无符号数,添加1,再转换回有符号数。结果将是:

  • 2的补码:-128(0b10000000)
  • 1的补码:-127(0b10000000)
  • 有符号数:-0(0b10000000)

这很混乱。但是如果我们想要定义溢出,我们该怎么办呢?假设我们有一个 signed char c = 127; 并且想要加1。我们可以定义结果应始终为-127,因为这是所有三个引用系统都能表示的(忽略这些不是唯一表示有符号整数的系统)。但这意味着编译器必须特别捕获并在2的补码(大多数系统)和有符号数系统上正确处理它,这将意味着额外的指令,从而降低这些机器的性能。

您在现实生活中很难遇到不使用二进制补码的计算机,所以C++开发人员是否可以强制要求使用呢?我没有找到任何现代CPU或DSP使用除二进制补码以外的东西,但是在C++创建时,存在使用1's补码的机器(例如CDC Cyber),我不会惊讶于听到一些DSP仍然在使用它们(毕竟,有些DSP具有 8位字符大小以外的尺寸)。这就是为什么它保持未定义行为的原因。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接