#c# #.net #clr
#c# #.net #clr
Вопрос:
Это приводит AccessViolationException
к выбросу:
using System;
namespace TestApplication
{
internal static class Program
{
private static unsafe void Main()
{
ulong* addr = (ulong*)Int64.MaxValue;
ulong val = *addr;
}
}
}
Это приводит NullReferenceException
к выбросу a:
using System;
namespace TestApplication
{
internal static class Program
{
private static unsafe void Main()
{
ulong* addr = (ulong*)0x000000000000FF;
ulong val = *addr;
}
}
}
Оба они являются недопустимыми указателями и оба нарушают правила доступа к памяти.
Почему NullReferenceException?
Комментарии:
1. Неясно, какую проблему программирования это решит. Это звучит как просто праздное любопытство.
2. Из любопытства, вы используете 64-разрядную или 32-разрядную версию? Может быть, это имеет значение?
3. @CoreyOgburn Я упомянул в комментарии к другому ответу, что он был 64-разрядным и что изменение на uint вместо ulong не исправило это.
Ответ №1:
Это вызвано дизайнерским решением Windows, принятым много лет назад. Нижние 64 килобайта адресного пространства зарезервированы. Доступ к любому адресу в этом диапазоне сообщается с исключением ссылки null вместо основного нарушения доступа. Это был мудрый выбор, нулевой указатель может производить чтение или запись по адресам, которые на самом деле не равны нулю. Например, при чтении поля объекта класса C оно имеет смещение от начала объекта. Если указатель на объект равен null, то код будет заблокирован при чтении по адресу, который больше 0.
У C # нет такой же проблемы, язык гарантирует, что нулевая ссылка будет перехвачена до того, как вы сможете вызвать метод экземпляра класса. Однако это зависит от языка, это не функция CLR. Вы можете написать управляемый код на C / CLI и генерировать ненулевые разыменования нулевого указателя. Вызов метода для объекта nullptr работает. Этот метод будет успешно выполнен. И вызовите другие методы экземпляра. Пока он не попытается получить доступ к переменной экземпляра или вызвать виртуальный метод, который требует разыменования этого, тогда бабах.
Гарантия C # очень хороша, она значительно упрощает диагностику проблем с нулевыми ссылками, поскольку они генерируются на сайте вызова и не взрываются где-то внутри вложенного метода. И это принципиально безопаснее, переменная экземпляра может не вызывать исключение для очень больших объектов, когда ее смещение больше 64 КБ. Довольно сложно сделать в управляемом коде, в отличие от C . Но не предоставляется бесплатно, объясняется в этом сообщении в блоге .
Комментарии:
1. Итак, если у меня есть объект с более чем 64 килобайтами полей, а затем я пытаюсь получить доступ к определенному полю из экземпляра этого объекта, когда ему присвоен null , он не выдаст a
NullReferenceException
, потому что смещение поля будет больше 65535 и, таким образом, вычисляется в адрес, превышающий этот?2. @JNZ — правильно. Хуже того, существует значительная вероятность того, что вы вообще не получите исключения, потому что память отображается по этому адресу. Что произошло, когда я попробовал это.
3. @Hans: Итак, можно ли сделать вывод, что
someReferenceTypeExpression.SomeField = someValue;
это принципиально небезопасное выражение, даже в C #, для массивного объекта?4. Да, программисты, которые пишут классы с более чем 4096 общедоступными полями, пишут принципиально неработающий код. Но все это уже знают 🙂
5. @Ani: Из моего тестирования доступ к полю со смещением за пределы 64 КБ все еще был заблокирован. Поскольку JIT знает смещения полей, ему не составит труда сгенерировать явную проверку null при доступе к полям со смещениями, превышающими 64 КБ, и опустить ее для полей с меньшими смещениями.
Ответ №2:
Исключение нулевой ссылки и исключение нарушения доступа вызываются процессором как нарушение доступа. Затем среда CLR должна угадать, следует ли специализировать нарушение доступа к исключению с нулевой ссылкой или оставить как более общее нарушение доступа.
Из ваших результатов видно, что среда CLR делает вывод, что нарушения доступа по адресам, очень близким к 0, вызваны нулевой ссылкой. Потому что они почти наверняка были сгенерированы нулевой ссылкой плюс смещение поля. Ваше использование небезопасного кода обманывает эту эвристику.
Комментарии:
1. Имея это в виду, это означало бы, что любой достаточно большой класс ошибочно вызовет исключение AccessViolationException при попытке получить к нему доступ, пока ему присвоен null, чего он не должен делать.
2. В этом случае среда CLR, вероятно, выполнит явное разыменование со смещением 0 перед доступом к полю, чтобы избежать ложного отрицания.
3. Точнее, эта эвристика исходит из того факта, что Windows естественным образом никогда не предоставит вам память ниже 64 КБ (вы можете принудительно использовать NtAllocateVirtualMemory — хотя вы даже не можете этого сделать на Windows8, кроме как в NTVDM — но я отвлекся). Следовательно, любой управляемый объект, вызывающий AV, является нулевым разыменованием объекта размером менее 64 КБ. Чтобы избежать случайного сообщения объектов> 64 КБ как AccessViolation, а не NullDereference, среда CLR автоматически «коснется» первого байта любого объекта> 64 КБ, прежде чем ссылаться на поле размером более 64 КБ с самого начала
4. @RaymondChen Среда CLR всегда знает истинное смещение поля, когда JIT достигает
ldfld
stfld
инструкции or . Если смещение поля больше размера области, в которой нарушение доступа к процессору приводит к aNullReferenceException
, он выдаст одну изcmp
инструкций для самого объекта, чтобы убедиться, что вы действительно видите правильное исключение. Явноеcmp
может быть (и есть) опущено для полей с меньшими смещениями.
Ответ №3:
Это может быть проблемой семантики.
Ваш первый пример пытается разыменовать указатель, содержимым которого является адрес Int64.MaxValue, а не указатель на переменную, имеющую значение Int64.MaxValue .
Похоже, вы пытаетесь прочитать значение, хранящееся по адресу Int64.MaxValue , которое, по-видимому, не входит в диапазон, принадлежащий вашему процессу.
Вы имеете в виду что-то вроде этого?
static unsafe void Main(string[] args)
{
ulong val = 1;// some variable space to store an integer
ulong* addr = amp;val;
ulong read = *addr;
Console.WriteLine("Val at {0} = {1}", (ulong)addr, read);
#if DEBUG
Console.WriteLine("Press enter to continue");
Console.ReadLine();
#endif
}
Комментарии:
1. Нет, просто поиграл и заметил несоответствие между генерируемыми исключениями. Любопытно, почему есть разница. Я понимаю, что адреса полностью недействительны для этого процесса.
2. @JNZ Я получаю исключение NullReferenceException для них обоих. Хммм.
Ответ №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. Поместите следующую строку в раздел Element файла конфигурации для приложения:
Другое
<legacyNullReferenceExceptionPolicy enabled = "1"/>
Комментарии:
1. Итак, с каких пор 0x000000000000FF совпадает с null?
2. Это не добавляет никакой новой информации, и я прошу прощения, но я не понимаю вашей точки зрения.