为什么C++下溢/溢出行为被视为未定义

Why is C++ underflow/overflow behaviour considered undefined?

本文关键字:未定义 溢出 C++ 下溢 为什么      更新时间:2023-10-16

我知道整数下溢和溢出是未定义的。

但是,鉴于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 或一个添加指令吗?那么,未定义的行为从何而来呢?

但是,鉴于C++最终编译为汇编,行为实际上不是定义的吗?

否,因为编译器决定它发出的程序集类型。如果编译器愿意,它可以生成程序集,如果遇到未定义的行为,它会擦除硬盘。

(实际上,"C++最终编译为汇编"甚至可能不是真的。例如,存在C++解释器,标准没有规定C++应该如何/编译成什么格式。

该标准的创建者决定不对其进行定义的原因之一是 - 几乎总是 - 优化的机会。例如,如果签名溢出是 UB,则编译器可以假设x + 1 > x始终为 true,并生成依赖于此前提条件的更简单/更短/更快的代码。

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

当你知道一个程序将要运行在一个平台上时,你可以对它的行为进行推理,但是如果没有这些知识,就不可能预测它的行为方式。

按位表示形式保持不变,整数格式保持不变

这根本不一定是真的。

IIRC,这是未定义的原因是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 的补码(大多数系统)和带符号量级系统上正确处理它,这意味着额外的指令,因此在这些机器上的性能不太理想。

你不太可能遇到在现实生活中不使用 2 补码的机器,所以C++人不能简单地强制要求它吗?我还没有发现任何当前的 CPU 或 DSP 使用 2 补码以外的任何东西,但是当创建 C++ 时,机器使用 1 的补码(例如 CDC Cyber),听到一些 DSP 今天仍然这样做,我不会感到惊讶(毕竟,有些 DSP 具有 8 位以外的char大小)。这就是为什么它保持未定义的行为。