Исключение нулевой ссылки происходит, когда вы разыменовываете нулевой указатель; CLR не волнует, является ли нулевой указатель небезопасным указателем с целым нулем, вставленным в него, или управляемым указателем (то есть ссылкой на объект ссылочного типа) с нулем, вставленным в него.
Как CLR узнает, что null был разыменован? И как CLR узнает, когда какой-то другой неверный указатель был разыменован? Каждый указатель указывает где-то на странице виртуальной памяти в адресном пространстве виртуальной памяти процесса. Операционная система отслеживает, какие страницы являются действительными, а какие недействительными; при касании недействительной страницы возникает исключение, которое обнаруживается CLR. Затем CLR отображает это как недопустимое исключение доступа или исключение нулевой ссылки.
Если недопустимый доступ к нижним 64 КБ памяти, это исключение null ref. В противном случае это недопустимое исключение доступа.
Это объясняет, почему разыменование ноль и единица дают исключение пустого 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]
, безусловно, выглядит как разыменование нуля! Так что это ошибка, о которой сообщается.