2012-11-01 11 views
11

Szukałem na P/Invoke deklarację RegOpenKeyEx gdy zauważyłem ten komentarz na stronie:Różnica między IntPtr i UIntPtr

Zmieniono IntPtr do UIntPtr: podczas wywoływania z IntPtr dla uchwytów, można uruchomić w Overflow. UIntPtr to właściwy wybór, jeśli chcesz, aby działał poprawnie na platformach 32- i 64-bitowych.

To nie ma sensu do mnie: zarówno IntPtr i UIntPtr mają reprezentować wskazówek więc ich wielkość powinna odpowiadać bitness systemu operacyjnego - albo 32 bity lub 64 bity. Ponieważ nie są to liczby, ale wskaźniki, ich podpisane wartości liczbowe nie powinny mieć znaczenia, tylko bity, które reprezentują adres, na który wskazują. Nie mogę wymyślić żadnego powodu, dla którego byłoby różnica między tymi dwoma, ale ten komentarz sprawił, że jestem niepewny.

Czy istnieje konkretny powód używania UIntPtr zamiast IntPtr? Według documentation:

typu IntPtr CLS jest zgodny, a typ UIntPtr nie. W środowisku wykonawczym wspólnego języka używany jest tylko typ IntPtr. Typ UIntPtr jest dostarczany głównie w celu zachowania symetrii architektury z typem IntPtr.

To oczywiście oznacza, że ​​nie ma różnicy (o ile ktoś nie próbuje przekonwertować wartości na liczby całkowite). Czy powyższy komentarz z serwisu pinvoke.net jest nieprawidłowy?

Edit:

Po przeczytaniu MarkH's answer, zrobiłem trochę sprawdzenia i okazało się, że aplikacje .NET nie są duże adres świadomy i może obsługiwać tylko 2 GB wirtualnej przestrzeni adresowej, gdy skompilowany w wersji 32-bitowej tryb. (Można użyć flagi hack, aby włączyć flagę dużego adresu, ale odpowiedź MarkH pokazuje, że kontrole w .NET Framework będą powodować błędy, ponieważ zakłada się, że przestrzeń adresowa ma tylko 2 GB, a nie 3 GB.)

Oznacza to, że wszystkie poprawne adresy pamięci wirtualnej, które może posiadać wskaźnik (w odniesieniu do .NET Framework) będą między 0x00000000 i 0x7FFFFFFF. Gdy ten zakres zostanie przetłumaczony na podpisany int, żadne wartości nie będą ujemne, ponieważ najwyższy bit nie jest ustawiony. To wzmacnia moje przekonanie, że nie ma żadnej różnicy w używaniu IntPtr vs UIntPtr. Czy moje rozumowanie jest poprawne?

Fermat2357 zaznaczył, że powyższa edycja jest nieprawidłowa.

+0

Zobacz również tutaj. http://stackoverflow.com/questions/1320296/intptr-vs-uintptr –

+1

@ Fermat2357 Tak, to było pytanie, które spowodowało, że napisałem nowe pytanie. – xxbbcc

+0

Myślę, że masz rację. Nie ma żadnej różnicy. "UCHWYT" jest po prostu unikalnym numerem dla zasobu (oczywiście w Win32 jest często implementowany jako wskaźnik 32-bitowy do obszaru pamięci).Jednakże 'IntPtr' jest w stanie osadzić dowolny 32-bitowy numer. Różnica w stosunku do UIntPtr to tylko interpretacja tego numeru nic więcej. –

Odpowiedz

9

UIntPtr i IntPtr wewnętrzne są realizowane jako

private unsafe void* m_value; 

Masz rację zarówno po prostu tylko zarządzający bity, które reprezentują adres.

Jedyne, co mogę myśleć o problemie z przepełnieniem, to próba wykonania arytmetyki wskaźnikowej. Obie klasy obsługują dodawanie i odejmowanie przesunięć. Ale także w tym przypadku binarna reprezentacja powinna być w porządku po takiej operacji.

Z mojego doświadczenia chciałbym również wybrać UIntPtr, ponieważ myślę, że na wskaźnik jako obiekt unsigned. Ale to nie jest istotne i tylko moja opinia.

Wygląda na to, że nie ma to znaczenia, jeśli używasz w swojej sprawie IntPtr lub UIntPtr.

EDIT:

IntPtr jest zgodny z CLS, ponieważ istnieją języki na górze CLR, który nie obsługuje niepodpisany.

+6

Znajduję tego rodzaju konstrukcję ramową, którą Microsoft bardzo niepokoił - mając duplikat pozbawionego znaczenia typu do zawijania wskaźników ze względu na symetrię. Powinny stworzyć właśnie jedną owijkę i nazwać ją SafePtr i nie wchodzić na podpisane/niepodpisane terytorium, co nie ma żadnego sensu. – xxbbcc

4

To oczywiście oznacza, że ​​nie ma różnicy (o ile ktoś nie próbuje przekonwertować wartości na liczby całkowite).

Niestety, framework próbuje to zrobić (gdy jest skompilowany specjalnie dla x86). Zarówno konstruktor IntPtr(long), jak i metody ToInt32() próbują odrzucić wartość do int w wyrażeniu checked. Oto implementacja widziana przy użyciu symboli debugowania szkieletu.

public unsafe IntPtr(long value) 
    { 
     #if WIN32 
      m_value = (void *)checked((int)value); 
     #else 
      m_value = (void *)value; 
     #endif 
    } 

Oczywiście sprawdzane wyrażenie wyrzuci wyjątek, jeśli wartość jest poza zakresem. Numer UIntPtr nie przepełnia się dla tej samej wartości, ponieważ próbuje on zamiast tego obsłużyć do uint.

+0

Zastanawiam się, czy ta kontrola jest na miejscu, ponieważ wirtualna przestrzeń adresowa użytkownika przechodzi od 0x00000000 do 0x7FFFFFFF. Ponieważ bit 31 nie jest ustawiony w poprawnym adresie pamięci wirtualnej, konwersja 'checked' nie powinna zakończyć się niepowodzeniem w 32-bitowym systemie operacyjnym. Nie jestem pewien, czy .NET obsługuje/3GB, ponieważ to rozszerza wirtualną przestrzeń adresową. – xxbbcc

+0

Są dwaj lekarze. Jedna 'public IntPtr (long value)' i druga 'public IntPtr (wartość int)'. Jest to całkowicie normalne (dla okien 32-bitowych), że pierwszy ctor akceptuje tylko 64-bitowe wartości, które można odlać do 32 bitów bez utraty precyzji. –

+0

@ Fermat2357 tak, to prawda, ale konwersja 'checked' nie polega na utracie precyzji, chodzi o sprawdzenie, czy adres mieści się w prawidłowym zakresie adresów. Teoretycznie kontrola nie powinna zawieść w systemach 32-bitowych, ponieważ najwyższy bit nie powinien być ustawiony w przestrzeni użytkownika. – xxbbcc

-1

Różnica między IntPtr \ UIntPtr jest taka sama jak różnic występujących pomiędzy: Int32 \ UInt32 (tzn., To wszystko o tym, jak numery się interpretować)

Zwykle, to nie ma znaczenia, który wybierzesz, ale, jak wspomniano, w niektórych przypadkach może wrócić, by cię ugryźć.

(nie jestem pewien, dlaczego MS wybrał IntPtr zacząć (CLS zgodności, etc ,.), pamięć jest traktowane jako DWORD (U32), co oznacza, że ​​jest bez znaku, a więc preferowana metoda powinna być UIntPtr, nie ? IntPtr, prawy)

Nawet: UIntPtr.Add()

Wydaje mi źle, to zajmuje UIntPtr jako wskaźnik, oraz 'int' dla offsetu. (Kiedy dla mnie "uint" miałoby więcej sensu, dlaczego karmić podpisaną wartość niepodpisaną metodą, kiedy, bardziej niż prawdopodobne, że kod wyrzucił ją do "uint" pod maską/facepalm)

I osobiście wolałbym UIntPtr od IntPtr tylko dlatego, że niepodpisane wartości pasują do wartości pamięci bazowej, z którą pracuję. :)


Btw, ja prawdopodobnie skończyć tworząc własny typ wskaźnika (używając UInt32), zbudowany specjalnie dla pracujących bezpośrednio z pamięci. (Zgaduję, że UIntPtr nie złapie wszystkich możliwych złych problemów z pamięcią, tj. 0xBADF00D, itd., Itd. Który jest CTD czekający na zdarzenie ... Będę musiał zobaczyć, jak wbudowany typ najpierw zajmuje się rzeczami, miejmy nadzieję, że kontrola zerowa \ zero poprawnie odfiltrowuje takie rzeczy.)