Почему доступ к памяти в нижнем адресном пространстве (но не пустой), как NullReferenceException.NET?

Это вызывает сброс AccessViolationException:

using System;

namespace TestApplication
{
    internal static class Program
    {
        private static unsafe void Main()
        {
            ulong* addr = (ulong*)Int64.MaxValue;
            ulong val = *addr;
        }
    }
}

Это вызывает сброс NullReferenceException:

using System;

namespace TestApplication
{
    internal static class Program
    {
        private static unsafe void Main()
        {
            ulong* addr = (ulong*)0x000000000000FF;
            ulong val = *addr;
        }
    }
}

Они оба являются недействительными указателями и нарушают правила доступа к памяти. Почему исключение NullReferenceException?

Ответ 1

Это вызвано решением Windows, принятым много лет назад. Дно 64 килобайт адресного пространства зарезервировано. Доступ к любому адресу в этом диапазоне сообщается с нулевым ссылочным исключением вместо основного нарушения прав доступа. Это был разумный выбор, нулевой указатель может производить чтение или запись по адресам, которые на самом деле не равны нулю. Например, чтение поля объекта класса С++ имеет смещение от начала объекта. Если указатель объекта равен нулю, код будет бомбить от чтения по адресу, который больше 0.

У С# нет такой же проблемы, язык гарантирует, что нулевая ссылка поймана, прежде чем вы сможете вызвать метод экземпляра класса. Это, однако, язык специфический, это не функция CLR. Вы можете написать управляемый код в С++/CLI и сгенерировать ненулевые нулевые указатели указателя. Вызывается метод для объекта nullptr. Этот метод будет весело выполнять. И вызовите другие методы экземпляра. Пока он не попытается получить доступ к переменной экземпляра или вызвать виртуальный метод, который требует разыменования этого, тогда kaboom.

Гарантия С# очень приятная, она облегчает диагностирование нулевых эталонных проблем, так как они генерируются на сайте вызова и не бомбардируют где-то внутри вложенного метода. И это принципиально безопасно, переменная экземпляра может не инициировать исключение на чрезвычайно больших объектах, когда его смещение больше 64 КБ. Довольно сложно сделать в управляемом коде btw, в отличие от С++. Но не приходит бесплатно, объясняется в этом сообщении .

Ответ 2

Исключение нулевой ссылки и исключение нарушения доступа оба повышаются CPU как нарушение доступа. CLR затем должен угадать, должно ли нарушение доступа быть специализированным для исключения с нулевой ссылкой или оставить как более общее нарушение прав доступа.

Из ваших результатов видно, что CLR обнаруживает, что нарушения доступа по адресам, очень близким к 0, вызваны нулевой ссылкой. Потому что они были почти наверняка сгенерированы нулевым опорным смещением поля плюс. Ваше использование небезопасного кода обманывает эту эвристику.

Ответ 3

Это может быть проблема семантики.

В первом примере вы пытаетесь разыменовать указатель, содержимое которого address Int64.MaxValue, а не указатель на переменную с значением для Int64.MaxValue.

Похоже, вы пытаетесь прочитать значение, хранящееся по адресу Int64.MaxValue, которое, по-видимому, не входит в диапазон, принадлежащий вашему процессу.

Вы имеете в виду что-то вроде этого?

        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
        }

Ответ 4

из http://msdn.microsoft.com/en-us/library/system.accessviolationexception.aspx

Информация о версии

Это исключение является новым в .NET Framework версии 2.0. В более ранних версии .NET Framework, нарушение прав доступа в неуправляемом коде или небезопасный управляемый код представлен исключением NullReferenceException в управляемый код. Исключение NullReferenceException также генерируется, когда значение null ссылка разыменована в проверяемом управляемом коде, возникновение что не связано с повреждением данных, и нет никакого способа различать две ситуации в версиях 1.0 или 1.1.

Администраторы могут разрешить выбранным приложениям вернуться к поведение .NET Framework версии 1.1. Поместите следующую строку в разделе Элемент файла конфигурации для Применение:

other <legacyNullReferenceExceptionPolicy enabled = "1"/>