7

在溢出标志的情况下,访问这个标志似乎对跨架构编程来说是一个巨大的福音。它将提供一种安全的替代方法来依赖未定义的行为来检查有符号整数溢出,例如:

if(a < a + 100) //detect overflow

我确实了解有安全的替代方案,例如:

if(a > (INT_MAX - 100)) //detected overflow

但是,似乎 C 和 C++ 语言都缺少对状态寄存器或其中的各个标志的访问。为什么不包含此功能,或者做出了哪些语言设计决定禁止包含此功能?

4

3 回答 3

8

因为 C 和 C++ 被设计为独立于平台的。状态寄存器不是。

如今,二进制补码普遍用于实现有符号整数运算,但并非总是如此。一个人的补码或符号和绝对值曾经很常见。在最初设计 C 语言时,这种 CPU 仍在普遍使用。例如,COBOL 区分负 0 和正 0,它们存在于那些架构上。显然,这些架构上的溢出行为是完全不同的!

顺便说一句,你不能依赖未定义的行为来检测溢出,因为合理的编译器在看到

if(a < a + 100)

将写一个警告并编译

if(true)

...(前提是优化已打开且特定优化未关闭)。

请注意,您不能依赖警告。编译器只会在条件结束truefalse等效转换之后发出警告,但在许多情况下,条件将在存在溢出的情况下被修改,而不会以普通true/结束false

于 2012-10-01T14:26:11.633 回答
6
  • 因为 C++ 被设计为一种可移植语言,即可以在许多 CPU 上编译的语言(例如 x86、ARM、LSI-11/2,以及 Game Boy、手机、冰柜、飞机、人体操作芯片和激光剑等设备)。
    • 跨 CPU 的可用标志可能有很大不同
    • 即使在同一个 CPU 中,标志也可能不同(采用 x86 标量指令与矢量指令)
    • 某些 CPU 甚至可能根本没有您想要的标志
  • 必须回答这个问题:当编译器无法确定它是否被使用时,是否应该始终提供/启用该标志?,这不符合您使用的不成文但神圣的 C 和 C++ 法律的报酬
  • 因为必须禁止编译器优化和重新排序代码以保持这些标志有效

后者的示例:

int x = 7;
x += z;
int y = 2;
y += z;

优化器可以将其转换为伪汇编代码:

alloc_stack_frame 2*sizeof(int)
load_int 7, $0
load_int 2, $1
add z, $0
add z, $1

这反过来会更类似于

int x = 7;
int y = 2;
x += z;
y += z; 

现在,如果您查询中间的寄存器

int x = 7;
x += z;
if (check_overflow($0)) {...}
int y = 2;
y += z;

然后在优化和反汇编之后,你可能会这样结束:

int x = 7;
int y = 2;
x += z;
y += z;
if (check_overflow($0)) {...}

那么这是不正确的。

可以构建更多示例,例如常量折叠编译时间溢出发生的情况。


旁注:我记得一个旧的 Borland C++ 编译器有一个小的 API 来读取当前的 CPU 寄存器。但是,上面关于优化的论点仍然适用。

在另一个旁注:检查溢出:

// desired expression: int z = x + y
would_overflow = x > MAX-y;

更具体

auto would_overflow = x > std::numeric_limits<int>::max()-y;

或者更好,不太具体:

auto would_overflow = x > std::numeric_limits<decltype(x+y)>::max()-y;
于 2012-10-01T14:32:20.767 回答
4

我可以想到以下几个原因。

  1. 通过允许访问寄存器标志,语言跨平台的可移植性受到严重限制。

  2. 优化器可以彻底改变表达式,并使您的标志无用。

  3. 它会使语言更复杂

  4. 大多数编译器都有大量的内在函数,可以在不借助标志的情况下执行最常见的操作(例如带进位的加法)。

  5. 大多数表达式可以以安全的方式重写以避免溢出。

  6. 如果您有非常特殊的需求,您总是可以退回到内联汇编

对状态寄存器的访问似乎还不足以进行标准化工作。

于 2012-10-01T14:26:13.637 回答