为什么默认情况下语言为什么不引发整数溢出错误?
在几种现代编程语言(包括C ++,Java和C#)中,该语言允许整数溢出在运行时发生,而不会引发任何类型的错误情况。
例如,考虑此(人为)方法,该方法不考虑上溢/下溢的可能性。 (为简便起见,该方法也不处理指定列表为空引用的情况。)
//Returns the sum of the values in the specified list. private static int sumList(List<int> list) { int sum = 0; foreach (int listItem in list) { sum += listItem; } return sum; }
如果按如下方式调用此方法:
List<int> list = new List<int>(); list.Add(2000000000); list.Add(2000000000); int sum = sumList(list);
sumList()方法中将发生溢出(因为Cis中的int类型是32位带符号整数,并且列表中值的总和超过了最大32位带符号整数的值)。 sum变量的值为-294967296(而不是4000000000);这很可能不是sumList方法的(假设)开发人员想要的。
显然,开发人员可以使用多种技术来避免整数溢出的可能性,例如使用Java的BigInteger或者C#中的checked关键字和/ checked编译器开关之类的类型。
但是,我感兴趣的问题是,为什么这些语言默认情况下设计为首先允许整数溢出发生,而不是例如在运行时执行操作时引发异常而导致异常。溢出。在开发人员在编写执行可能导致溢出的算术运算的代码时忽略了溢出的可能性时,这种行为似乎将有助于避免错误。 (这些语言可能包含类似"未经检查"的关键字之类的东西,它们可以指定一个块,在该块中允许整数溢出发生而不会引发异常,在这种情况下,开发人员明确地打算该行为;实际上是这样的。)
答案是否只是归结为性能-语言设计者不希望其各自的语言默认为具有"慢速"算术整数运算,而运行时将需要做额外的工作来检查每个适用的算术运算是否发生了溢出-并且在不小心发生溢出的情况下,这种性能考虑的价值超过了避免"无提示"故障的价值?
除了性能方面的考虑之外,是否还有其他原因可以做出这种语言设计决策?
解决方案
我认为性能是一个很好的理由。如果考虑典型程序中的每条指令都增加一个整数,并且不是简单的op加1,它每次都要检查加1是否会使类型溢出,那么额外周期的开销将非常大。
性能可能达到99%。在x86上,必须检查每个操作上的溢出标志,这将对性能造成巨大影响。
其余的1%将覆盖人们在进行有符号位操作或者在混合有符号和无符号操作时"不精确"并且想要溢出语义的情况。
因为检查溢出需要时间。通常会转换为单个汇编指令的每个原始数学运算都必须包括对溢出的检查,从而导致产生多个汇编指令,从而可能导致程序慢几倍。
我对为什么默认情况下不会在运行时引发错误的理解可以归结为希望创建具有类似ACID行为的编程语言的传统。具体来说,我们所做的任何事情都将执行(或者不进行编码)的原则将被执行(或者不执行)。如果我们没有编写一些错误处理程序的代码,那么机器将由于没有错误处理程序而"假设"我们真的想做要告诉它做的可笑的,容易崩溃的事情。
(ACID参考:http://en.wikipedia.org/wiki/ACID)
向后兼容性很大。使用C,假定我们已经足够注意数据类型的大小,如果发生上溢/下溢,这就是我们想要的。然后使用C ++和Cand Java,"内置"数据类型的工作方式几乎没有改变。
我们在整数溢出始终是不希望有的行为的假设下工作。
有时整数溢出是所需的行为。我看到的一个示例是将绝对航向值表示为定点数。给定无符号整数,0为0或者360度,最大32位无符号整数(0xffffffff)为正好在360度以下的最大值。
int main() { uint32_t shipsHeadingInDegrees= 0; // Rotate by a bunch of degrees shipsHeadingInDegrees += 0x80000000; // 180 degrees shipsHeadingInDegrees += 0x80000000; // another 180 degrees, overflows shipsHeadingInDegrees += 0x80000000; // another 180 degrees // Ships heading now will be 180 degrees cout << "Ships Heading Is" << (double(shipsHeadingInDegrees) / double(0xffffffff)) * 360.0 << std::endl; }
与本示例类似,可能在其他情况下也可以接受溢出。
C / C ++从不强制执行陷阱行为。即使明显的被0除也是C ++中的未定义行为,而不是指定的陷阱。
C语言没有捕获的任何概念,除非我们对信号进行计数。
C ++有一个设计原则,除非我们要求,否则不会引入C中不存在的开销。因此,Stroustrup并不想强制要求整数以需要任何显式检查的方式运行。
一些早期的编译器以及用于受限硬件的轻量级实施根本不支持异常,并且通常可以使用编译器选项禁用异常。对内置的语言强制规定例外情况将是有问题的。
即使C ++对整数进行了检查,但如果出于性能提升的考虑而关闭,早期的99%的程序员都将关闭...
在C#中,这是性能问题。具体而言,即开即用的基准测试。
当Cwa是新的时,Microsoft希望很多C ++开发人员会改用它。他们知道许多C ++人士认为C ++速度很快,尤其是比那些在自动内存管理等方面"浪费"时间的语言更快。
潜在的采用者和杂志审阅者都可能会获得新的C#的副本,进行安装,构建一个琐碎的应用程序,使其在现实世界中没人能写出来,在紧要的循环中运行并测量花费了多长时间。然后,他们会为自己的公司做出决定,或者根据该结果发表文章。
他们的测试表明Cto的速度比本地编译的C ++慢,这一事实会使人们迅速关闭C ++。Capp将自动捕获上溢/下溢的事实是他们可能会错过的事情。因此,默认情况下处于关闭状态。
我认为很明显,我们希望/ check on的时间达到了99%。这是一个不幸的妥协。