有没有试过用你最喜欢的编程语言把所有数字从1总结到2,000,000?结果很容易手工计算: 2,000,001,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,
C#打印出-1453759936 -一个负值!我猜Java也是这样做的。
这意味着有一些常见的编程语言在默认情况下忽略算术溢出(在C#中,有一些隐藏的选项可以更改该溢出)。在我看来,这是一种非常危险的行为,阿丽亚娜5号的坠毁不是由这种溢出造成的吗?
那么:这种危险行为背后的设计决策是什么呢?
编辑:
对这个问题的第一个回答表示检查的成本过高。让我们执行一个简短的C#程序来测试这个假设:
Stopwatch watch = Stopwatch.StartNew();
checked
{
for (int i = 0; i < 200000; i++)
{
int sum = 0;
for (int j = 1; j < 50000; j++)
{
sum += j;
}
}
}
watch.Stop();
Console.WriteLine(watch.Elapsed.TotalMilliseconds);在我的机器上,选中的版本需要11015 my,而未检查的版本需要4125 my。也就是说,检查步骤花费的时间几乎是添加数字的两倍(总共是原始时间的3倍)。但是经过10,000,000次的重复,一次检查所花费的时间仍然小于1纳秒。在某些情况下,这一点很重要,但对于大多数应用程序来说,这并不重要。
编辑2:
我用/p:CheckForOverflowUnderflow="false"参数(通常,我打开溢出检查)重新编译服务器应用程序( Windows分析从几个传感器接收到的数据,涉及相当多的解码),并将其部署到设备上。Nagios监视显示,平均CPU负载保持在17%。
这意味着在上面的示例中发现的性能命中与我们的应用程序完全无关。
发布于 2017-05-08 10:34:51
这有三个原因:
发布于 2017-05-08 15:45:25
谁说这是个糟糕的交易?!
我在启用溢出检查的情况下运行所有的生产应用程序。这是一个C#编译器选项。实际上,我对此进行了比较,但我无法确定差异。访问数据库以生成(非玩具) HTML的成本超过了溢出检查成本。
我确实赞赏这样一个事实,即我知道生产中没有任何操作溢出。在溢出的情况下,几乎所有代码的行为都会不正常。虫子不会是良性的。数据有可能损坏,安全问题也有可能发生。
如果我需要性能(有时也是这样),我将禁用基于粒度的使用unchecked {}的溢出检查。当我想说我依赖的是一个没有溢出的操作时,我可能会冗余地将checked {}添加到代码中来记录这个事实。我注意到溢出,但我不一定要感谢检查。
我认为C#团队在默认情况下选择不检查溢出时做了错误的选择,但由于强烈的兼容性考虑,这种选择现在已被封杀。请注意,这一选择是在2000年左右作出的。硬件能力较弱,.NET还没有很大的吸引力。也许.NET想以这种方式吸引Java和C/C++程序员。.NET也意味着能够接近金属。这就是为什么它有不安全的代码、结构和强大的本机调用能力,所有这些都是Java所没有的。
我们的硬件获得的速度越快,聪明的out编译器就会在默认情况下得到更有吸引力的溢出检查。
我还认为溢出检查通常比无限大小的数字要好。无限大小的数字具有更高的性能成本,更难优化(我相信),它们打开了无限资源消耗的可能性。
JavaScript处理溢出的方法更糟糕。JavaScript数字是浮点双倍。“溢出”显示为保留完全精确的整数集。会出现一些轻微错误的结果(例如,用一个--这可以将有限的循环转化为无限的循环)。
对于某些语言,如C/C++,默认情况下检查溢出显然是不合适的,因为用这些语言编写的应用程序类型需要最基本的性能。尽管如此,人们还是努力让C/C++成为一种更安全的语言,允许选择一种更安全的模式。这是值得赞扬的,因为90%-99%的代码往往是冷的。一个例子是强制2's补码包装的fwrapv编译器选项。这是编译器的“实现质量”特性,而不是语言特性。
Haskell没有逻辑调用堆栈,也没有指定的计算顺序。这使得异常发生在不可预测的点上。在a + b中,未指定是否首先计算a或b,以及这些表达式是否完全终止。因此,Haskell大部分时间使用无界整数是有意义的。这种选择适用于纯函数式语言,因为在大多数Haskell代码中,异常确实是不合适的。除以零确实是Haskells语言设计中的一个问题。与无界整数不同,它们也可以使用固定宽度的包装整数,但这不符合语言特性的“关注正确性”主题。
溢出异常的另一种替代方法是由未定义的操作创建并通过操作传播的有害值(如浮点NaN值)。这似乎比溢出检查要昂贵得多,并且使所有操作都变得更慢,而不仅仅是那些可能失败的操作(除了通常具有的硬件加速,以及通常不存在的ints -尽管Itanium有NaT,它“不是一件事”)。我也不太明白,让程序继续软弱无力地处理坏数据的意义。就像ON ERROR RESUME NEXT。它隐藏错误,但无助于获得正确的结果。supercat指出,有时这样做是一种性能优化。
发布于 2017-05-08 10:35:52
因为这是一个坏的权衡,使所有的计算更昂贵,以便自动捕获罕见的情况下,溢出确实发生。与其让所有程序员为他们不使用的功能付出代价,不如让程序员认识到这是一个问题的罕见情况并添加特殊的预防措施。
https://softwareengineering.stackexchange.com/questions/348535
复制相似问题