为什么语言在默认情况下会在整数溢出时引发错误?
在几种现代编程语言(包括C ++,Java和C#)中,该语言允许在运行时发生整数溢出 ,而不会引起任何types的错误情况。
例如,考虑这个(人为的)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()
方法中会发生溢出(因为C#中的int
types是一个32位有符号整数,并且列表中的值的总和超过了最大32位有符号整数的值)。 sumvariables的值是-294967296(不是4000000000的值)。 这最有可能不是sumList方法的(假设的)开发者所期望的。
显然,开发人员可以使用各种技术来避免整数溢出的可能性,例如在Java中使用BigInteger
types或C#中的checked
关键字和/checked
编译器开关。
然而,我感兴趣的问题是为什么这些语言默认情况下允许整数溢出发生,而不是在例如运行时执行操作时引发exception溢出。 看起来像这样的行为将有助于避免在编写执行可能导致溢出的算术运算的代码时开发人员忽略可能性溢出的情况下的错误。 (这些语言可能包含类似“unchecked”关键字的东西,可以指定一个允许在不引发exception的情况下允许发生整数溢出的块,在这种情况下,开发人员明确指定了这种行为; C#实际上确实有这种情况 。 )
答案只是归结为性能 – 语言devise者不希望他们各自的语言默认为具有“慢”的算术整数操作,其中运行时需要做额外的工作来检查是否发生溢出,在每个适用的算术操作 – 这种性能上的考虑超过了在无意溢出的情况下避免“无声”故障的价值?
除了性能方面的考虑之外,这个语言devise决定还有其他的原因吗?
在C#中,这是一个性能问题。 具体而言,即开即用的基准testing。
当C#是新的时候,微软希望很多C ++开发人员会转向它。 他们知道许多C ++人认为C ++是快速的,特别是比在自动内存pipe理等上“浪费”时间的语言更快。
潜在的采用者和杂志评论员都可能会得到一个新的C#的副本,安装它,build立一个没有人会写在现实世界中的小应用程序,运行在一个紧密的循环,并测量花了多长时间。 然后,他们会为他们的公司做出决定,或根据这个结果发表一篇文章。
事实上,他们的testing显示C#比本地编译的C ++慢,这种事情会让人们很快地把C#closures。 事实上,你的C#应用程序将会自动捕获溢出/下溢是他们可能错过的那种事情。 所以默认closures
我认为很明显,我们希望/被检查的时间有99%。 这是一个不幸的妥协。
我认为performance是一个很好的理由。 如果你考虑在一个典型的程序中增加一个整数的每一个指令,而不是简单的操作来加1,它必须每次检查是否加1会溢出的types,那么额外的周期成本将非常严重。
你的工作假设整数溢出总是不受欢迎的行为。
有时整数溢出是期望的行为。 我见过的一个例子是将绝对航向值表示为一个定点数。 给定一个无符号整数,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; }
可能还有其他情况下溢出是可以接受的,类似于这个例子。
这可能是99%的performance。 在x86上,必须检查每个操作的溢出标志,这将是一个巨大的性能打击。
另外1%将覆盖人们在混合有符号和无符号操作时进行奇怪的位操作或“不精确”的情况,并希望溢出语义。
因为检查溢出需要时间。 每个基本的math运算(通常转换为单个汇编指令)都必须包含对溢出的检查,从而产生多个汇编指令,可能会导致程序速度降低数倍。
C / C ++从不强制陷阱行为。 即使明显除以0也是C ++中的未定义行为,而不是指定types的陷阱。
C语言没有任何陷阱的概念,除非你计数信号。
C ++有一个devise原则,它不会介绍C中不存在的开销,除非你要求它。 所以Stroustrup不会要求强制整数行为的方式需要任何明确的检查。
一些早期的编译器和受限硬件的轻量级实现,根本不支持exception,通常可以通过编译器选项禁用exception。 为内置语言规定例外将是有问题的。
即使C ++已经对整数进行了检查,但在早期,99%的程序员会因为性能提升而closures。
向后兼容性是一个大的。 对于C语言,假设你对数据types的大小有足够的关注,如果发生了上溢/下溢,那就是你想要的。 然后,随着C ++,C#和Java,“内置”数据types的工作方式几乎没有变化。
我理解为什么在运行时默认不会引发错误归结为希望创build具有类ACID行为的编程语言的遗留问题。 具体来说,任何你编码的东西(或不编码)的信条,它会做(或不做)。 如果你没有编写一些error handling程序,那么机器就会凭借没有error handling程序的“假设”,你真的想要做一个荒谬的,易于崩溃的事情,你告诉它做的事情。
(ACID参考: http : //en.wikipedia.org/wiki/ACID )