Co to jest typ danych uintptr_t

Odpowiedzi:

198

uintptr_tjest typem całkowitym bez znaku, który może przechowywać wskaźnik danych. Co zwykle oznacza, że ​​ma ten sam rozmiar co wskaźnik.

Jest opcjonalnie zdefiniowany w C ++ 11 i późniejszych standardach.

Częstym powodem, dla którego chcemy mieć typ liczby całkowitej, który może przechowywać typ wskaźnika architektury, jest wykonywanie operacji na wskaźniku dla liczb całkowitych lub zaciemnianie typu wskaźnika, podając go jako „uchwyt” liczby całkowitej.

Edycja: Zauważ, że Steve Jessop ma kilka bardzo interesujących dodatkowych szczegółów (których nie będę kradł) w innej odpowiedzi dla ciebie pedantycznych typów :)

Drew Dormann
źródło
53
Zauważ, że size_twystarczy tylko pomieścić największy obiekt i może on być mniejszy niż wskaźnik. Można się tego spodziewać na architekturach segmentowanych, takich jak 8086 (16 bitów size_t, ale 32 bity void*)
MSalters
3
do reprezentowania różnicy między dwoma wskaźnikami, masz ptrdiff_t. uintptr_tnie jest do tego przeznaczony.
lipiec
3
@jalf: Dla różnicy tak, ale dla odległości chciałbyś typu bez znaku.
Drew Dormann,
definicja uintptr_t najwyraźniej nie jest obowiązkowa (więc nie jest standardem) nawet w C ++ 11! cplusplus.com/reference/cstdint (dostałem podpowiedź od odpowiedzi Steve'a Jessopa)
Antonio
2
@MarcusJ unsigned intzwykle nie jest wystarczająco duży. Ale może być wystarczająco duży. Ten typ istnieje specjalnie po to, by usunąć wszystkie „założenia” .
Drew Dormann
238

Po pierwsze, w momencie zadawania pytania uintptr_tnie było w C ++. Jest w C99 <stdint.h>, jako opcjonalny typ. Wiele kompilatorów C ++ 03 udostępnia ten plik. Jest również w C ++ 11, w <cstdint>którym ponownie jest opcjonalny i który odnosi się do C99 dla definicji.

W C99 definiuje się go jako „nieoznaczony typ liczb całkowitych z tą właściwością, że dowolny prawidłowy wskaźnik na void może zostać przekonwertowany na ten typ, a następnie z powrotem przekształcony na wskaźnik na void, a wynik będzie porównywalny z pierwotnym wskaźnikiem”.

Weź to na myśli, co mówi. Nie mówi nic o rozmiarze.

uintptr_tmoże być tego samego rozmiaru co void*. Może być większy. Może być mniejszy, chociaż taka implementacja C ++ jest perwersyjna. Na przykład na jakiejś hipotetycznej platformie, na której void*jest 32 bity, ale używane są tylko 24 bity wirtualnej przestrzeni adresowej, możesz mieć 24 bity, uintptr_tktóre spełniają wymagania. Nie wiem, dlaczego implementacja miałaby to zrobić, ale standard na to pozwala.

Steve Jessop
źródło
8
Dzięki za „<stdint.h>”. Moja aplikacja nie skompilowała się z powodu deklaracji uintptr_t. Ale kiedy czytam twój komentarz, dodaję „#include <stdint.h>” i tak, teraz działa. Dzięki!
JavaRunner
2
Na przykład, aby przydzielić wyrównaną pamięć ?
legends2k
4
Innym typowym przypadkiem użycia rzutowania wskaźnika na wartość int jest utworzenie nieprzezroczystego uchwytu, który ukrywa wskaźnik. Jest to przydatne w przypadku zwracania odwołania do obiektu z interfejsów API, w których chcesz zachować prywatność obiektu w bibliotece i uniemożliwić aplikacjom dostęp do niego. Aplikacja jest następnie zmuszona do używania interfejsu API do wykonywania dowolnych operacji na obiekcie
Joel Cunningham,
3
@JelCunningham: to działa, ale tak naprawdę nie różni się niczym od używania void*. Wpływa to jednak na możliwe przyszłe kierunki, szczególnie jeśli możesz chcieć zmienić, aby użyć czegoś, co tak naprawdę jest tylko uchwytem liczby całkowitej, a nie konwertowanym wskaźnikiem.
Steve Jessop
2
@CiroSantilli 事件 事件 2016 六四 事件 法轮功 Typowym przypadkiem użycia jest przekazanie tylko int do interfejsu API, który oczekuje nieważności * na dane ogólne. Oszczędza naciśnięcia klawiszy typedef struct { int whyAmIDoingThis; } SeriouslyTooLong; SeriouslyTooLong whyAmNotDoneYet; whyAmINotDoneYet.whyAmIDoingThis = val; callback.dataPtr = &whyAmINotDoneYet;. Zamiast: callback.dataPtr = (void*)val. Z drugiej strony oczywiście dostajesz void*i musisz rzucić go z powrotem int.
Francesco Dondi
19

Jest to liczba całkowita bez znaku dokładnie o rozmiarze wskaźnika. Ilekroć musisz zrobić coś niezwykłego ze wskaźnikiem - na przykład odwróć wszystkie bity (nie pytaj dlaczego), rzuć to uintptr_ti manipuluj nim jak zwykłą liczbą całkowitą, a następnie rzuć z powrotem.

sharptooth
źródło
6
Oczywiście możesz to zrobić, ale byłoby to oczywiście niezdefiniowane zachowanie. Myślę, że wtedy jedyną rzeczą, którą możesz zrobić z wynikiem rzutowania na uintptr_t, jest przekazanie go bez zmian i rzutowanie go z powrotem - wszystko inne to UB.
śleske,
Są chwile, w których trzeba grać bitami, a to normalnie generowałoby błędy kompilatora. Typowym przykładem jest wymuszanie 16-bajtowej pamięci wyrównanej dla niektórych aplikacji wideo i wydajności o kluczowym znaczeniu. stackoverflow.com/questions/227897/…
Chmura
3
@sleske to nieprawda. Na maszynach z samowyrównującymi się typami dwa najmniej znaczące bity wskaźnika będą wynosić zero (ponieważ adresy są wielokrotnościami 4 lub 8). Widziałem programy, które wykorzystują to do kompresji danych.
saadtaame
3
@saadtaame: Właśnie zauważył, że to UB według standardu C . Nie oznacza to, że nie można tego zdefiniować w niektórych systemach - kompilatory i środowiska wykonawcze mogą swobodnie definiować określone zachowanie dla czegoś, co jest standardem UB w standardzie C. Więc nie ma sprzeczności :-).
śleske,
2
Niekoniecznie jest to dokładnie rozmiar wskaźnika. Wszystkie standardowe gwarancje są takie, że konwersja void*wartości wskaźnika do uintptr_tiz powrotem daje void*wartość, która jest równa pierwotnemu wskaźnikowi. uintptr_tma zwykle taki sam rozmiar jak void*, ale nie jest to gwarantowane, ani nie gwarantuje, że bity przekonwertowanej wartości mają jakieś szczególne znaczenie. I nie ma gwarancji, że może przechowywać skonwertowaną wartość wskaźnika do funkcji bez utraty informacji. Wreszcie nie ma gwarancji istnienia.
Keith Thompson,
15

Istnieje już wiele dobrych odpowiedzi na część „co to jest typ danych uintptr_t”. Spróbuję odpowiedzieć na pytanie „do czego można go wykorzystać?” udział w tym poście.

Głównie dla operacji bitowych na wskaźnikach. Pamiętaj, że w C ++ nie można wykonywać bitowych operacji na wskaźnikach. Z powodów zobacz Dlaczego nie możesz wykonywać bitowych operacji na wskaźniku w C i czy jest na to jakiś sposób?

Tak więc, aby wykonać operacje bitowe na wskaźnikach, trzeba rzucić wskaźniki, aby wpisać unitpr_t, a następnie wykonać operacje bitowe.

Oto przykład funkcji, którą właśnie napisałem, aby wykonać bitową wyłączność lub 2 wskaźników do przechowywania na połączonej liście XOR, abyśmy mogli przechodzić w obu kierunkach jak podwójnie połączona lista, ale bez kary za przechowywanie 2 wskaźników w każdym węźle .

 template <typename T>
 T* xor_ptrs(T* t1, T* t2)
 {
     return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(t1)^reinterpret_cast<uintptr_t>(t2));
  }
BGR
źródło
1
inne niż arytmetyka bitów, dobrze jest również, jeśli chcesz mieć semantykę opartą na adresach zamiast zliczania obiektów.
Alex
4

Korzystając z ryzyka uzyskania kolejnej odznaki Nekromanty, chciałbym dodać jedno bardzo dobre zastosowanie dla uintptr_t (lub nawet intptr_t), a mianowicie pisanie testowalnego kodu osadzonego. Piszę głównie kod osadzony skierowany do różnych procesorów uzbrojenia i obecnie tensiliki. Mają one różną natywną szerokość magistrali, a tensilica jest w rzeczywistości architekturą Harvarda z osobnymi magistralami kodu i danych, które mogą mieć różne szerokości. Używam stylu programowania opartego na testach dla większości mojego kodu, co oznacza, że ​​wykonuję testy jednostkowe dla wszystkich jednostek kodu, które piszę. Testowanie jednostkowe na rzeczywistym sprzęcie docelowym jest kłopotliwe, więc zwykle piszę wszystko na komputerze z procesorem Intel w systemie Windows lub Linux, używając Ceedling i GCC. To powiedziawszy, wiele osadzonego kodu wymaga skręcania bitów i manipulacji adresami. Większość moich komputerów Intela jest 64-bitowych. Więc jeśli zamierzasz przetestować kod manipulacji adresem, potrzebujesz uogólnionego obiektu do wykonania matematyki. Dlatego uintptr_t daje niezależny od komputera sposób debugowania kodu przed próbą wdrożenia na docelowym sprzęcie. Kolejny problem dotyczy niektórych maszyn, a nawet modeli pamięci niektórych kompilatorów, wskaźniki funkcji i wskaźniki danych mają różne szerokości. Na tych komputerach kompilator może nawet nie zezwalać na rzutowanie między dwiema klasami, ale uintptr_t powinien mieć możliwość przechowywania obu.

bd2357
źródło
Nie jestem pewien, czy dotyczy ... Czy próbowałeś już „nieprzejrzystych typów maszyn”? Vide: youtube.com/watch?v=jLdSjh8oqmE
shycha
@ slleske Chciałbym, żeby był dostępny w C. Ale posiadanie stdint.h jest lepsze niż nic. (Życzę też, aby wyliczenia były silniejsze, ale większość debuggerów dobrze je
rozgryza