This causes an AccessViolationException
to be thrown:
using System;
namespace TestApplication
{
internal static class Program
{
private static unsafe void Main()
{
ulong* addr = (ulong*)Int64.MaxValue;
ulong val = *addr;
}
}
}
This causes a NullReferenceException
to be thrown:
using System;
namespace TestApplication
{
internal static class Program
{
private static unsafe void Main()
{
ulong* addr = (ulong*)0x000000000000FF;
ulong val = *addr;
}
}
}
They're both invalid pointers and both violate memory access rules.
Why the NullReferenceException?
This is caused by a Windows design decision made many years ago. The bottom 64 kilobytes of the address space is reserved. An access to any address in that range is reported with a null reference exception instead of the underlying access violation. This was a wise choice, a null pointer can produce reads or writes at addresses that are not actually zero. Reading a field of a C++ class object for example, it has an offset from the start of the object. If the object pointer is null then the code will bomb from reading at an address that's larger than 0.
C# doesn't have quite the same problem, the language guarantees that a null reference is caught before you can call an instance method of a class. This is however language specific, it is not a CLR feature. You can write managed code in C++/CLI and generate non-zero null pointer dereferences. Calling a method on a nullptr object works. That method will merrily execute. And call other instance methods. Until it tries to access an instance variable or call a virtual method, which requires dereferencing this, kaboom then.
The C# guarantee is very nice, it makes diagnosing null reference problems much easier since they are generated at the call site and don't bomb somewhere inside a nested method. And it is fundamentally safer, the instance variable might not trigger an exception on extremely large objects when its offset is larger than 64K. Pretty hard to do in managed code btw, unlike C++. But doesn't come for free, explained in this blog post.
A null reference exception and an access violation exception are both raised by the CPU as an access violation. The CLR then has to guess whether the access violation should be specialized to a null reference exception or left as the more general access violation.
It is evident from your results that the CLR infers that access violations at addresses very close to 0 are caused by a null reference. Because they were almost certainly generated by a null reference plus field offset. Your use of unsafe code fools this heuristic.
This may be a semantics issue.
Your first example is trying to dereference a pointer whose content is the address Int64.MaxValue, not a pointer to a variable that has a value of Int64.MaxValue.
Looks like you're trying to read the value stored at the address Int64.MaxValue, which is, apparently not in the range that's owned by your process.
Do you mean something like this?
static unsafe void Main(string[] args)
{
ulong val = 1;// some variable space to store an integer
ulong* addr = &val;
ulong read = *addr;
Console.WriteLine("Val at {0} = {1}", (ulong)addr, read);
#if DEBUG
Console.WriteLine("Press enter to continue");
Console.ReadLine();
#endif
}
from http://msdn.microsoft.com/en-us/library/system.accessviolationexception.aspx
Version Information
This exception is new in the .NET Framework version 2.0. In earlier
versions of the .NET Framework, an access violation in unmanaged code
or unsafe managed code is represented by a NullReferenceException in
managed code. A NullReferenceException is also thrown when a null
reference is dereferenced in verifiable managed code, an occurrence
that does not involve data corruption, and there is no way to
distinguish between the two situations in versions 1.0 or 1.1.
Administrators can allow selected applications to revert to the
behavior of the .NET Framework version 1.1. Place the following line
in the Element section of the configuration file for the
application:
other <legacyNullReferenceExceptionPolicy enabled = "1"/>