为什么没有对CPU's状态寄存器在C和C++设计中的应用

Why was no intrinsic access to the CPU's status register in the design of both C and C++?

本文关键字:C++ 应用 寄存器 状态 CPU 为什么      更新时间:2023-10-16

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

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)

(前提是启用了优化并且未关闭特定优化)。

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

  • 因为C++被设计成一种可移植的语言,即在许多CPU上编译的语言(例如x86、ARM、LSI-11/2,以及Game Boys、手机、冰箱、飞机、人类操纵芯片和激光剑等设备)。
    • 不同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;

我能想到以下原因。

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

  2. 优化器可能会大幅更改表达式,并使您的标志变得无用。

  3. 这将使语言更加复杂

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

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

  6. 如果你有非常特殊的需求,你总是可以回到内联组装

对状态寄存器的访问似乎还不够,无法进行标准化工作。