Co to jest uintptr_t
i do czego można go wykorzystać?
Co to jest uintptr_t
i do czego można go wykorzystać?
Odpowiedzi:
uintptr_t
jest 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 :)
size_t
wystarczy 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*
)
ptrdiff_t
. uintptr_t
nie jest do tego przeznaczony.
unsigned int
zwykle 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” .
Po pierwsze, w momencie zadawania pytania uintptr_t
nie 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_t
moż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_t
które spełniają wymagania. Nie wiem, dlaczego implementacja miałaby to zrobić, ale standard na to pozwala.
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.
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
.
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_t
i manipuluj nim jak zwykłą liczbą całkowitą, a następnie rzuć z powrotem.
void*
wartości wskaźnika do uintptr_t
iz powrotem daje void*
wartość, która jest równa pierwotnemu wskaźnikowi. uintptr_t
ma 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.
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));
}
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.