为什么 C 和 C++ 不提供一组实现提供的操作来执行每个基本整数操作并提供溢出检查(例如 a bool safeAdd(int *out, int a, int b)
)。
据我了解,大多数指令集都有方法来判断操作是否溢出(例如 x86 溢出和进位标志),并且在有符号整数的情况下也会发生定义。
因此,编译器不应该比用 C 和 C++ 编写的代码做得更好,创建更简单和更快的操作吗?
为什么 C 和 C++ 不提供一组实现提供的操作来执行每个基本整数操作并提供溢出检查(例如 a bool safeAdd(int *out, int a, int b)
)。
据我了解,大多数指令集都有方法来判断操作是否溢出(例如 x86 溢出和进位标志),并且在有符号整数的情况下也会发生定义。
因此,编译器不应该比用 C 和 C++ 编写的代码做得更好,创建更简单和更快的操作吗?
C 和 C++ 遵循“你不需要为不需要的东西付费”的中心原则。因此,默认算术运算不会偏离底层架构的单一算术运算指令。
至于为什么没有标准库函数来添加两个整数和检测溢出,我不能说。首先,该语言似乎将有符号整数溢出定义为未定义的行为:
在 C 编程语言中,有符号整数溢出会导致未定义的行为,
考虑到有多种方法可以实现有符号整数(一个补码、二进制补码等),并且在创建 C 时,这些架构都很普遍,这是可以理解的为什么这是未定义的。如果没有大量关于底层平台的信息,就很难实现一个“安全*”的纯 C函数。它可以在逐个 CPU 的基础上完成。
但这并不意味着不可能。如果有人可以通过更安全的溢出助手找到向 C 或 C++ 标准机构提出的建议,并且能够了解它们被拒绝的原因,我肯定会感兴趣。
无论如何,在实践中有很多方法可以检测算术溢出和库来提供帮助。
可能是因为没有需求。算术溢出是未定义的行为,明确表示允许实现进行此类检查。如果编译器供应商认为这样做会销售更多编译器,他们会的。
实际上,编译器比程序员更有效地完成它们将非常非常困难。验证所有数字输入的范围几乎是标准程序,可以证明以后的操作不会溢出的范围。所有优秀的程序员都习惯于这样做。所以这意味着if
输入后立即快速,无需进一步检查。
尽管如此,众所周知,程序员会犯错误,而且当您稍后更改计算时很容易忘记更正验证。我想在编译器中看到这样的功能。但显然,它不会帮助销售编译器,或者至少供应商认为它不会,所以我们不明白。
因为它很少需要。您何时真正需要检测整数溢出?在几乎所有需要检查某个范围的情况下,通常由您定义实际范围,因为该范围完全取决于应用程序和算法。
什么时候你真的需要知道结果是否超出了范围,int
而不是知道结果是否在特定算法的允许域内,或者索引是否在数组的范围内?是你给你的变量一个语义,语言规范只为你提供类型的整体范围,如果你选择的类型不符合你的需要,那是你的错。
整数溢出是 UB,因为你很少真正关心它。如果我unsigned char
在操作过程中溢出,我可能选择了错误的类型来累积 1000 万个数字。但是在运行时了解溢出对我没有帮助,因为我的设计无论如何都被破坏了。
一个更好的问题可能是:为什么整数溢出未定义行为?实际上,99.9% 的 CPU 使用二进制补码和进位/溢出位。所以在现实世界中,在汇编器/操作码级别,整数溢出总是定义明确的。事实上,很多汇编程序或与硬件相关的 C 语言都严重依赖于定义明确的整数溢出(尤其是定时器硬件的驱动程序)。
最初的 C 语言,在标准化之前,可能没有详细考虑过这样的事情。但是当 C 被 ANSI 和 ISO 标准化时,它们必须遵循一定的标准化规则。不允许ISO标准偏向于某种技术,从而在竞争中赋予一定的公司优势。
所以他们不得不考虑一些 CPU 可能会实现一些模糊的东西,比如补码、“符号和大小”或“一些实现定义的方式”。他们必须允许有符号的零、填充位和其他晦涩的有符号整数机制。
正因为如此,有符号数的行为变得非常模糊。你不知道当 C 中的有符号整数溢出时会发生什么,因为有符号整数可能用二进制补码、一个补码或其他一些实现定义的疯狂表示。因此整数溢出是未定义的行为。
这个问题的明智解决方案不是发明一些安全范围检查,而是声明 C 语言中的所有有符号整数都应具有二进制补码格式,故事结束。那么 unsigned char 将始终为 0 到 127 并溢出到 -128 并且一切都将被明确定义。但是人为的标准官僚机构阻止了标准变得理智。
C标准中有很多这样的问题。对齐/填充,字节序等。
为什么?好吧,因为当 C++ 从那开始时它们还没有在 C 中,并且因为从那时起没有人提出这样的函数并成功地说服编译器制造商和委员会成员相信它们足够有用,可以提供。
请注意,编译器确实 提供了此类内在函数,因此并不是它们反对它们。
还要注意,有一些命题可以标准化定点算术和无界精度整数类型之类的东西。
所以可能只是兴趣不够。