当前位置:  开发笔记 > 编程语言 > 正文

为什么语言默认情况下不会在整数溢出上引发错误?

如何解决《为什么语言默认情况下不会在整数溢出上引发错误?》经验,为你挑选了6个好方法。

在几种现代编程语言(包括C++,Java和C#)中,该语言允许在运行时发生整数溢出,而不会引发任何类型的错误条件.

例如,考虑这个(人为的)C#方法,它没有考虑上溢/下溢的可能性.(为简洁起见,该方法也不处理指定列表为空引用的情况.)

//Returns the sum of the values in the specified list.
private static int sumList(List list)
{
    int sum = 0;
    foreach (int listItem in list)
    {
        sum += listItem;
    }
    return sum;
}

如果调用此方法如下:

List list = new List();
list.Add(2000000000);
list.Add(2000000000);
int sum = sumList(list);

sumList()方法中将发生溢出(因为intC#中的类型是32位有符号整数,并且列表中值的总和超过了最大32位有符号整数的值).sum变量的值为-294967296(不是值4000000000); 这很可能不是sumList方法的(假设的)开发人员所期望的.

显然,开发人员可以使用各种技术来避免整数溢出的可能性,例如使用类似Java的类型BigInteger,或者使用C#中的checked关键字和/checked编译器开关.

但是,我感兴趣的问题是为什么这些语言默认设计为允许整数溢出首先发生,而不是例如在运行时执行操作时引发异常,从而导致溢出.看起来这种行为有助于避免在编写执行可能导致溢出的算术运算的代码时开发人员忽略解释溢出可能性的情况下的错误.(这些语言可能包含类似"未经检查"的关键字,它可以指定允许发生整数溢出而不会引发异常的块,在开发人员明确意图该行为的情况下; C#实际上确实有这个. )

答案简单归结为性能 - 语言设计者不希望他们各自的语言默认具有"慢"算术整数运算,其中运行时需要做额外的工作来检查是否发生溢出,在每个适用的算术上操作 - 这种性能考虑超过了在无意溢出发生时避免"无声"故障的价值?

除了性能考虑之外,还有其他原因可以做出这种语言设计决策吗?



1> Jay Bazuzi..:

在C#中,这是一个性能问题.具体而言,开箱即用的基准测试.

当C#是新的时,微软希望很多C++开发人员能够切换到它.他们知道许多C++人认为C++是快速的,尤其比那些"浪费"在自动内存管理等方面的语言更快.

潜在的采用者和杂志评论者都可能获得新C#的副本,安装它,构建一个无人能够在现实世界中编写的简单应用程序,在紧密的循环中运行它,并测量它花了多长时间.然后他们会为他们的公司做出决定或根据该结果发表文章.

事实上,他们的测试表明C#比本机编译的C++慢,这种事情可以让人们迅速摆脱C#.您的C#应用​​程序将自动捕获溢出/下溢的事实是他们可能会错过的事情.所以,它默认是关闭的.

我认为很明显我们想要/检查的时间是99%.这是一个不幸的妥协.


请记住,C#现在已经12岁了.当时,许多程序员认为"C++速度快,Java速度慢,C#就像Java".今天,我认为,大多数程序员看到产品上市时间和管理复杂性对紧密循环优化对业务成功的影响更大.
整数溢出检查会对实际基准测试产生多大影响?在我看来,C#还有许多其他的东西要糟糕得多(例如,给定一个字段`readonly rect foo;`,一个像'DoSomething(foo.X,foo.Y`)这样的语句;`需要复制一个` foo`,调用它的`X`访问器方法,制作`foo`的另一个副本,并调用它的`Y`访问器方法.非常大的开销 - 足以使整数溢出检查看起来相当微不足道.

2> David Hill..:

我认为表演是一个很好的理由.如果你考虑典型程序中的每个指令增加一个整数,并且如果不是简单的op加1,它必须每次检查如果添加1会溢出该类型,那么额外周期的成本将非常严重.


当操作溢出时CPU是否不会给你一个标志?(当然,检查该标志也需要时间).

3> Doug T...:

您假设整数溢出始终是不期望的行为.

有时整数溢出是期望的行为.我见过的一个例子是将绝对航向值表示为固定点数.给定unsigned int,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;

}

可能还有其他情况可以接受溢出,类似于此示例.


可能有更多的实例,其中整数溢出产生错误的结果然后它产生正确的结果.实际上,它实际生成正确结果的唯一时间是它是预期的行为,并且它实际上被用作特征,例如在您的示例中.
@Dan:请注意,代码使用uint32_t.这保证是32位(因此名称:-)).所以作者*确实*想到了这一点.
@sleske:如果你检查编辑,你会看到Doug的原始帖子使用了"unsigned int"...因此我的评论.也许我的评论让他纠正了他的代码......
更不用说依赖于像这样的特定于平台的低级细节的固有危险.如果在64位平台上重新编译会发生什么?伊克.

4> Steve Jessop..:

C/C++从不强制要求陷阱行为.即使明显除以0也是C++中未定义的行为,而不是指定类型的陷阱.

除非你计算信号,否则C语言没有任何陷阱概念.

C++有一个设计原则,它不会引入C中不存在的开销,除非你要求它.所以Stroustrup不希望强制整数的行为方式需要任何明确的检查.

一些早期编译器和受限硬件的轻量级实现根本不支持异常,并且通常可以使用编译器选项禁用异常.强制语言内置函数的异常将是有问题的.

即使C++已经对整数进行了检查,99%的程序员在早期就会因性能提升而关闭...



5> Dima..:

因为检查溢出需要时间.通常转换为单个汇编指令的每个原始数学运算都必须包括对溢出的检查,从而导致多个汇编指令,从而可能导致程序慢几倍.



6> Rob Walker..:

这可能是99%的表现.在x86上必须检查每个操作的溢出标志,这将是一个巨大的性能损失.

另外1%将覆盖那些人们正在进行奇特的操作或者在混合有符号和无符号操作时"不精确"并且想要溢出语义的情况.

推荐阅读
mobiledu2402851203
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有