在溢出标志的情况下,访问这个标志似乎对跨架构编程来说是一个巨大的福音。它将提供一种安全的替代方法来依赖未定义的行为来检查有符号整数溢出,例如:
if(a < a + 100) //detect overflow
我确实了解有安全的替代方案,例如:
if(a > (INT_MAX - 100)) //detected overflow
但是,似乎 C 和 C++ 语言都缺少对状态寄存器或其中的各个标志的访问。为什么不包含此功能,或者做出了哪些语言设计决定禁止包含此功能?
在溢出标志的情况下,访问这个标志似乎对跨架构编程来说是一个巨大的福音。它将提供一种安全的替代方法来依赖未定义的行为来检查有符号整数溢出,例如:
if(a < a + 100) //detect overflow
我确实了解有安全的替代方案,例如:
if(a > (INT_MAX - 100)) //detected overflow
但是,似乎 C 和 C++ 语言都缺少对状态寄存器或其中的各个标志的访问。为什么不包含此功能,或者做出了哪些语言设计决定禁止包含此功能?
因为 C 和 C++ 被设计为独立于平台的。状态寄存器不是。
如今,二进制补码普遍用于实现有符号整数运算,但并非总是如此。一个人的补码或符号和绝对值曾经很常见。在最初设计 C 语言时,这种 CPU 仍在普遍使用。例如,COBOL 区分负 0 和正 0,它们存在于那些架构上。显然,这些架构上的溢出行为是完全不同的!
顺便说一句,你不能依赖未定义的行为来检测溢出,因为合理的编译器在看到
if(a < a + 100)
将写一个警告并编译
if(true)
...(前提是优化已打开且特定优化未关闭)。
请注意,您不能依赖警告。编译器只会在条件结束true
或false
等效转换之后发出警告,但在许多情况下,条件将在存在溢出的情况下被修改,而不会以普通true
/结束false
。
后者的示例:
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;
我可以想到以下几个原因。
通过允许访问寄存器标志,语言跨平台的可移植性受到严重限制。
优化器可以彻底改变表达式,并使您的标志无用。
它会使语言更复杂
大多数编译器都有大量的内在函数,可以在不借助标志的情况下执行最常见的操作(例如带进位的加法)。
大多数表达式可以以安全的方式重写以避免溢出。
如果您有非常特殊的需求,您总是可以退回到内联汇编
对状态寄存器的访问似乎还不足以进行标准化工作。