Разница между IntPtr и UIntPtr
Я смотрел декларацию P/Invoke RegOpenKeyEx
, когда заметил этот комментарий на странице:
Изменен IntPtr
на UIntPtr
: при вызове IntPtr
для дескрипторов вы перейдете в переполнение. UIntPtr
- правильный выбор, если вы хотите, чтобы это правильно работало на 32- и 64-разрядных платформах.
Это не имеет большого значения для меня: оба IntPtr
и UIntPtr
должны представлять указатели, поэтому их размер должен соответствовать битности ОС - либо 32 бита, либо 64 бита. Поскольку это не цифры, а указатели, их числовые значения, указанные в них, не должны иметь значения, а только биты, представляющие адрес, на который они указывают. Я не могу придумать какой-либо причины, по которой была бы разница между этими двумя, но этот комментарий сделал меня неопределенным.
Есть ли конкретная причина использовать UIntPtr
вместо IntPtr
? В соответствии с документация:
Тип IntPtr
CLS-совместимый, а тип UIntPtr
- нет. В режиме общего языка используется только тип IntPtr
. Тип UIntPtr
предоставляется в основном для сохранения архитектурной симметрии с типом IntPtr
.
Это, конечно, подразумевает, что нет никакой разницы (если кто-то не пытается преобразовать значения в целые числа). Так неверно ли приведенный комментарий от pinvoke.net?
Edit
После прочтения ответа MarkH я немного проверял и выяснил, что приложения .NET не имеют большого адреса и могут обрабатывать только 2GB виртуальные адресное пространство при компиляции в 32-битном режиме. (Можно использовать взломать, чтобы включить флаг большого адреса, но ответ MarkH показывает, что проверки внутри .NET Framework будут ломать вещи, потому что предполагается, что адресное пространство только 2 ГБ, а не 3 ГБ.)
Это означает, что все правильные адреса виртуальной памяти, которые может иметь указатель (насколько это касается .NET Framework), будут находиться между 0x00000000 и 0x7FFFFFFF. Когда этот диапазон будет переведен на подписанный int
, никакие значения не будут отрицательными, потому что старший бит не установлен. Это подтверждает мое убеждение, что нет никакой разницы в использовании IntPtr vs UIntPtr. Правильно ли мои рассуждения?
Fermat2357 указал, что указанное выше изменение неверно.
Ответы
Ответ 1
UIntPtr
и IntPtr
внутренне реализованы как
private unsafe void* m_value;
Вы правы и просто управляете битами, которые представляют адрес.
Единственное, что я могу думать о проблеме переполнения, - это попытаться выполнить арифметику указателей. Оба класса поддерживают добавление и вычитание смещений. Но и в этом случае двоичное представление должно быть в порядке после такой операции.
По моему опыту, я бы также предпочел UIntPtr
, потому что я думаю о указателе как неподписанном объекте. Но это не имеет значения и только мое мнение.
Кажется, не имеет никакого значения, если вы используете IntPtr
или UIntPtr
в своем случае.
EDIT:
IntPtr
является CLS-совместимым, потому что есть языки поверх CLR, которые не поддерживают unsigned.
Ответ 2
Это, конечно, подразумевает, что нет никакой разницы (пока кто-то не пытается преобразовать значения в целые числа).
К сожалению, инфраструктура пытается сделать именно это (при компиляции специально для x86). И конструктор IntPtr(long)
, и методы ToInt32()
пытаются передать значение в int
в выражении checked
. Здесь реализована реализация, использующая символы отладки фреймов.
public unsafe IntPtr(long value)
{
#if WIN32
m_value = (void *)checked((int)value);
#else
m_value = (void *)value;
#endif
}
Конечно, проверенное выражение будет генерировать исключение, если значение вне границ. UIntPtr
не переполняется для одного и того же значения, потому что он пытается использовать вместо uint
.
Ответ 3
Разница между IntPtr\UIntPtr совпадает с различиями между Int32\UInt32 (т.е. все о том, как интерпретируются числа.)
Обычно, это не имеет значения, какой вы выбираете, но, как уже упоминалось, в некоторых случаях он может вернуться, чтобы укусить вас.
(Я не уверен, почему MS выбрала IntPtr для начала (CLS Compliance и т.д.), память обрабатывается как DWORD (u32), что означает, что она без знака, поэтому предпочтительным методом должен быть UIntPtr, а не IntPtr, правильно?)
Even: UIntPtr.Add()
Кажется неправильным для меня, он принимает UIntPtr как указатель и "int" для смещения. (Когда для меня "uint" будет иметь больший смысл. Зачем подавать подписанное значение в неподписанный метод, когда, скорее всего, код передал его "uint" под капотом. /Facepalm )
Я лично предпочел бы UIntPtr над IntPtr просто потому, что неподписанные значения соответствуют значениям базовой памяти, с которыми я работаю.:)
Btw, я, скорее всего, создаю свой собственный тип указателя (используя UInt32), созданный специально для работы непосредственно с памятью. (Я предполагаю, что UIntPtr не собирается улавливать все возможные проблемы с плохой памятью, то есть 0xBADF00D и т.д., И т.д. Что ждет CTD... Мне нужно посмотреть, как встроенный тип сначала обрабатывает вещи, надеюсь, нуль\нуль проверяет правильную фильтрацию таких вещей.)