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

和自甴很熟 提交于 2019-12-20 16:29:27

问题


For educational purposes, I'm writing a set of methods that cause runtime exceptions in C# to understand what all the exceptions are and what causes them. Right now, I'm tinkering with programs that cause an AccessViolationException.

The most obvious way (to me) to do this was to write to a protected memory location, like this:

System.Runtime.InteropServices.Marshal.WriteInt32(IntPtr.Zero, 0);

Just as I had hoped, this threw an AccessViolationException. I wanted to do it more concisely, so I decided to write a program with unsafe code, and do (what I thought was) exactly the same thing by assigning 0 to the zero-pointer.

unsafe
{
    *(int*)0 = 0;
}

For reasons that elude me, this throws a NullReferenceException. I played around with it some and found out that using *(int*)1 instead also throws a NullReferenceException, but if you use a negative number, like *(int*)-1 it will throw an AccessViolationException.

What's going on here? Why does *(int*)0 = 0 cause a NullReferenceException, and why doesn't it cause an AccessViolationException?


回答1:


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.




回答2:


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.




回答3:


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.




回答4:


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.




回答5:


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.



来源:https://stackoverflow.com/questions/8681090/why-doesnt-int0-0-cause-an-access-violation

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