Array range checking in .NET

Posted: (EET/GMT+2)

 

Sometimes, people ask me how range checking in .NET works. And also, if range checking is different in Delphi for .NET and C#.

Indeed, there are slight differences, but they really don't matter much. A demonstration is in order here. Let's assume a simple application that creates and array with two elements, and then tries to access the third and non-existing element.

For instance like this:

Delphi code:
------------
SetLength(Arr,2);
Arr[1] := 123;
Arr[2] := 234;
Arr[3] := 345;

C# code:
--------
int[] ints = new int[2];
ints[0] = 123;
ints[1] = 234;
ints[2] = 345;

And the results in MSIL (from the C# compiler):

IL_0001: newarr [mscorlib]System.Int32
IL_0006:  stloc.0
IL_0007:  ldloc.0
IL_0008:  ldc.i4.0
IL_0009:  ldc.i4.s 123
IL_000b:  stelem.i4
IL_000c:  ldloc.0
IL_000d:  ldc.i4.1
IL_000e:  ldc.i4 0xea
IL_0013:  stelem.i4
IL_0014:  ldloc.0
IL_0015:  ldc.i4.2
IL_0016:  ldc.i4 0x159
IL_001b:  stelem.i4

When this code is run, the application raises an exception of type System.IndexOutOfRangeException with the message "Index was outside the bounds of the array.".

In .NET, there isn't much you could do to disable array bounds checking, since setting an array element is done my the "stelem" MSIL instruction. And according to the SDK docs, the instruction always raises an exception if the index is out of bounds.

Surely, C# has the "checked" and "unchecked" keywords, but these only help in overflows in integer arithmetic, not when accessing arrays. So, it seems you just cannot disable array bound checking.

But in Delphi, we have the {$R} compiler directive (or {$RANGECHECKS} if you with to use the long name). Can this directive be used to control range checking? Unfortunately, the answer is "No."

A simple test is in order:

{$R-}
  SetLength(Arr,2);
  Arr[1] := 123;
  Arr[2] := 234;
  Arr[3] := 345;
{$R+}

Indeed, we still get the same IndexOutOfRangeException error message. Turning the checks on instead causes the error message to change:

{$R+}
  SetLength(Arr,2);
  Arr[1] := 123;
  Arr[2] := 234;
  Arr[3] := 345;
{$R-}

Now the error message simply becomes this: "Borland.Delphi.ERangeError: Range check error".

So, changing the range checking option in Delphi simply changes the type of the exception thrown. But just like in C#, one cannot circumvent the checks. This could be bad for performance, but very good for delivering quality code. I always suggest people compiling their code with range checks on in Win32.

If you don't believe me and are using Delphi to develop Win32 applications, check out Dan Miser's post about Delphi's range checking in his blog.