Разница между 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... Мне нужно посмотреть, как встроенный тип сначала обрабатывает вещи, надеюсь, нуль\нуль проверяет правильную фильтрацию таких вещей.)