在几种现代编程语言(包括C++,Java和C#)中,该语言允许在运行时发生整数溢出,而不会引发任何类型的错误条件.
例如,考虑这个(人为的)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;
}
Run Code Online (Sandbox Code Playgroud)
如果调用此方法如下:
List<int> list = new List<int>();
list.Add(2000000000);
list.Add(2000000000);
int sum = sumList(list);
Run Code Online (Sandbox Code Playgroud)
sumList()方法中将发生溢出(因为intC#中的类型是32位有符号整数,并且列表中值的总和超过了最大32位有符号整数的值).sum变量的值为-294967296(不是值4000000000); 这很可能不是sumList方法的(假设的)开发人员所期望的.
显然,开发人员可以使用各种技术来避免整数溢出的可能性,例如使用类似Java的类型BigInteger,或者使用C#中的checked关键字和/checked编译器开关.
但是,我感兴趣的问题是为什么这些语言默认设计为允许整数溢出首先发生,而不是例如在运行时执行操作时引发异常,从而导致溢出.看起来这种行为有助于避免在编写执行可能导致溢出的算术运算的代码时开发人员忽略解释溢出可能性的情况下的错误.(这些语言可能包含类似"未经检查"的关键字,它可以指定允许发生整数溢出而不会引发异常的块,在开发人员明确意图该行为的情况下; C#实际上确实有这个. )
答案简单归结为性能 - 语言设计者不希望他们各自的语言默认具有"慢"算术整数运算,其中运行时需要做额外的工作来检查是否发生溢出,在每个适用的算术上操作 - 这种性能考虑超过了在无意溢出发生时避免"无声"故障的价值?
除了性能考虑之外,还有其他原因可以做出这种语言设计决策吗?
处理整数溢出是一项常见任务,但在C#中处理它的最佳方法是什么?是否有一些语法糖比其他语言更简单?或者这真的是最好的方式吗?
int x = foo();
int test = x * common;
if(test / common != x)
Console.WriteLine("oh noes!");
else
Console.WriteLine("safe!");
Run Code Online (Sandbox Code Playgroud) 该表达式int.Minvalue / -1根据C#规范导致实现定义的行为:
7.8.2分部运营商
如果左操作数是最小的可表示的int或long值而右操作数是-1,则发生溢出.在已检查的上下文中,这会导致抛出System.ArithmeticException(或其子类).在未经检查的上下文中,它是实现定义的,是否抛出System.ArithmeticException(或其子类)或溢出未报告,结果值是左操作数的值.
测试程序:
var x = int.MinValue;
var y = -1;
Console.WriteLine(unchecked(x / y));
Run Code Online (Sandbox Code Playgroud)
这会引发OverflowException.NET 4.5 32位,但它没有.
为什么规范会保留结果实现定义?这是反对这样做的情况:
idiv在这种情况下,x86 指令总是会导致异常.同样有趣的是,如果x / y是一个编译时常量我们确实得到unchecked(int.MinValue / -1) == int.MinValue:
Console.WriteLine(unchecked(int.MinValue / -1)); //-2147483648
Run Code Online (Sandbox Code Playgroud)
这意味着,x / y可以有根据使用的语法形式在不同的行为(而不是只依赖于的值x和y).这是规范允许的,但它似乎是一个不明智的选择.为什么C#这样设计?
一个类似的问题指出,在说明书这一确切行为被规定,但它并没有(足够的)回答为什么语言是这样设计的.不讨论替代选择.
List(Of T)当我将内部_version字段设置为Integer.MaxValue并添加新项时,是什么阻止了抛出算术溢出异常?
TL; DR:
正如您在查看源代码时所看到的那样,没有针对潜在算术溢出的保护.但是,Integer.MinValue如果值是最大值,那么某些事物就会将值设置为.
<__DynamicallyInvokable()> _
Public Sub Add(ByVal item As T)
If (Me._size = Me._items.Length) Then
Me.EnsureCapacity((Me._size + 1))
End If
Me._items(Me._size++) = item
Me._version += 1
End Sub
Run Code Online (Sandbox Code Playgroud)
详细
由于List(Of T)的内部项数组是私有的,我不会使用反射来访问它,我决定使用原始List(Of T)源代码创建自定义列表.
但是,我注意到,如果版本达到Integer.MaxValue,则无法防止潜在的算术溢出.
Me._version += 1
Run Code Online (Sandbox Code Playgroud)
所以我修改了代码:
Me._version = If((Me._version = Integer.MaxValue), Integer.MinValue, (Me._version + 1I))
Run Code Online (Sandbox Code Playgroud)
现在开始有趣的部分; 出于好奇,我设置了一个测试,我使用反射将内部_version字段设置为最大值.
Dim flags As BindingFlags = (BindingFlags.Instance Or BindingFlags.NonPublic)
Dim list As New List(Of String) …Run Code Online (Sandbox Code Playgroud) 这段代码如下: -
int x = -24;
uint y = (uint) x;
Console.WriteLine("*****" + y + "********");
// o/p is *****4294967272********
Run Code Online (Sandbox Code Playgroud)
为什么在C#中这种行为,详细阐述会有所帮助.谢谢你们.