Подтвердить что ты не робот

Почему не * (int *) 0 = 0 вызывает нарушение доступа?

В образовательных целях я пишу набор методов, которые вызывают исключения во время выполнения на С#, чтобы понять, что все исключения и что их вызывает. Прямо сейчас, я занимаюсь программами, которые вызывают AccessViolationException.

Самый очевидный способ (для меня) - записать в защищенную память, например:

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

Как я и надеялся, это бросило AccessViolationException. Я хотел сделать это более сжато, поэтому решил написать программу с небезопасным кодом и сделать то, что я думал, точно так же, назначив 0 нулевому указателю.

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

По причинам, которые ускользают от меня, это вызывает a NullReferenceException. Я немного поиграл с ним, и выяснил, что вместо *(int*)1 вместо *(int*)1 вызывается NullReferenceException, но если вы используете отрицательное число, например *(int*)-1, оно выдает AccessViolationException.

Что здесь происходит? Почему *(int*)0 = 0 вызывает NullReferenceException, и почему он не вызывает AccessViolationException?

4b9b3361

Ответ 1

Исключение пустой ссылки происходит, когда вы разыскиваете нулевой указатель; CLR не заботится о том, является ли нулевой указатель небезопасным указателем с нулевым в нем нулевым значением или управляемым указателем (то есть ссылкой на объект ссылочного типа) с нулевым вложением в него.

Как CLR знает, что null был разыменован? И как CLR знает, когда какой-либо другой недействительный указатель был разыменован? Каждый указатель указывает где-то на странице виртуальной памяти в адресном пространстве виртуальной памяти процесса. Операционная система отслеживает, какие страницы действительны и являются недопустимыми; когда вы касаетесь недействительной страницы, возникает исключение, обнаруженное CLR. CLR затем создает это как исключение недопустимого доступа или исключение с нулевой ссылкой.

Если недопустимый доступ находится в нижней части 64K памяти, это исключение null ref. В противном случае это недопустимое исключение доступа.

Это объясняет, почему разыменование нуля и один дают исключение null ref и почему разыменование -1 дает недопустимое исключение доступа; -1 - указатель 0xFFFFFFFF на 32-битных машинах, и эта конкретная страница (на машинах x86) всегда зарезервирована для использования операционной системой в своих целях. Пользовательский код не может получить к нему доступ.

Теперь вы можете разумно спросить, почему не просто сделать исключение ссылочной ссылки для нулевого указателя и исключение недопустимого доступа для всего остального? Поскольку большая часть времени, когда небольшое число разыменовывается, происходит потому, что вы дошли до него с помощью нулевой ссылки. Представьте себе, например, что вы пытались сделать:

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

Компилятор переводит это в моральный эквивалент:

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

который является разыменованием 4. Но с точки зрения пользователя p[1], безусловно, выглядит как разыменование нуля! Итак, это сообщение об ошибке.

Ответ 2

Это не ответ сам по себе, но если вы декомпилируете WriteInt32, вы обнаружите, что он ловит NullReferenceException и выбрасывает AccessViolationException. Таким образом, поведение, вероятно, одно и то же, но замаскировано реальным исключением, и возникает другое исключение.

Ответ 3

NullReferenceException утверждает, что "Исключение, которое возникает при попытке разыменовать ссылку на нулевой объект", так как *(int*)0 = 0 пытается установить ячейку памяти 0x000 с помощью разыменования объекта, она выкинет NullReferenceException. Обратите внимание, что это исключение выдается перед попыткой получить доступ к памяти.

С другой стороны, класс AccessViolationException указывает, что: "Исключение, которое возникает при попытке прочитать или записать защищенный memory", и поскольку System.Runtime.InteropServices.Marshal.WriteInt32(IntPtr.Zero, 0) не использует разыменования, вместо этого пытается установить память с помощью этого метода, объект не разыменован, поэтому означает, что не будет выбрано NullReferenceException.

Ответ 4

MSDN четко говорит:

В программах, состоящих полностью из проверяемого управляемого кода, все ссылки являются действительными или нулевыми, а нарушения прав доступа - невозможно. AccessViolationException возникает только при проверке управляемый код взаимодействует с неуправляемым кодом или с небезопасным управляемым кодом код.

См. AccessViolationException справка.

Ответ 5

Вот как работает CLR. Вместо проверки, если адрес объекта == null для каждого доступа к полю, он просто получает к нему доступ. Если это было null - CLR ловит GPF и реконструирует его как NullReferenceException. Независимо от того, что это за ссылка.