Why doesn't *(int*)0=0 cause an access violation?

谁都会走 提交于 2019-12-03 04:27:52

A null reference exception happens when you dereference a null pointer; the CLR does not care whether the null pointer is an unsafe pointer with the integer zero stuck into it or a managed pointer (that is, a reference to an object of reference type) with zero stuck into it.

How does the CLR know that null has been dereferenced? And how does the CLR know when some other invalid pointer has been dereferenced? Every pointer points to somewhere in a page of virtual memory in the virtual memory address space of the process. The operating system keeps track of which pages are valid and which are invalid; when you touch an invalid page it raises an exception which is detected by the CLR. The CLR then surfaces that as either an invalid access exception or a null reference exception.

If the invalid access is to the bottom 64K of memory, it's a null ref exception. Otherwise it is an invalid access exception.

This explains why dereferencing zero and one give a null ref exception, and why dereferencing -1 gives an invalid access exception; -1 is pointer 0xFFFFFFFF on 32 bit machines, and that particular page (on x86 machines) is always reserved for the operating system to use for its own purposes. User code cannot access it.

Now, you might reasonably ask why not just do the null reference exception for pointer zero, and invalid access exception for everything else? Because the majority of the time when a small number is dereferenced, it is because you got to it via a null reference. Imagine for example that you tried to do:

int* p = (int*)0;
int x = p[1];

The compiler translates that into the moral equivalent of:

int* p = (int*)0;
int x = *( (int*)((int)p + 1 * sizeof(int)));

which is dereferencing 4. But from the user's perspective, p[1] surely looks like a dereference of null! So that is the error that is reported.

This isn't an answer per se, but if you decompile WriteInt32 you find it catches NullReferenceException and throws an AccessViolationException. So the behavior is likely the same, but is masked by the real exception being caught and a different exception being raised.

The NullReferenceException states that "The exception that is thrown when there is an attempt to dereference a null object reference", so since *(int*)0 = 0 tries to set memory location 0x000 using an object dereference it will throw a NullReferenceException. Note that this Exception is thrown before trying to even access the memory.

The AccessViolationException class on the other hand states that, "The exception that is thrown when there is an attempt to read or write protected memory", and since System.Runtime.InteropServices.Marshal.WriteInt32(IntPtr.Zero, 0) does not use a dereference, instead tries to set the memory using this method, an object is not dereferenced, therefore meaning no NullReferenceException will be thrown.

The MSDN says that clearly:

In programs consisting entirely of verifiable managed code, all references are either valid or null, and access violations are impossible. An AccessViolationException occurs only when verifiable managed code interacts with unmanaged code or with unsafe managed code.

See AccessViolationException help.

This is how CLR work. Instead of checking if object address == null for every field access, it just access it. If it was null - CLR catches GPF and rethrow it like NullReferenceException. No matter what kind of reference it was.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!