2017-07-27 101 views
7

Sekcja 7.18.1.4 standardowych stanach C99:Czy konwersja na `void *` jest wymagana przed konwersją wskaźnika na `uintptr_t` i na odwrót?

Poniższy typ wyznacza unsigned całkowitą o tej własności, że każdy ważny wskaźnik do void można przeprowadzić tego typu, a następnie przekształcany z powrotem do wskaźnik do void, a wynik będzie porównać równe oryginalnej palików:

uintptr_t

Czy to oznacza, że ​​tylko typy void * mogą zostać zastąpione uintptr_t i powrotem bez zmiany wartości wskaźnika początku?

W szczególności chciałbym wiedzieć, czy następujący kod jest wymagane do korzystania uintptr_t:

int foo = 42; 
void * bar = &foo; 
uintptr_t baz = bar; 
void * qux = baz; 
int quux = *(int *)qux; /* quux == foo == 42 */ 

Albo jeśli to prostsza wersja jest gwarantowana przez standard C99 doprowadzić do tego samego efektu:

int foo = 42; 
uintptr_t bar = &foo; 
int baz = *(int *)bar; /* baz == foo == 42 */ 

Czy konieczna jest konwersja na void * przed konwersją wskaźnika na uintptr_t i na odwrót?

+0

Tak, wymagany jest rzut pośredni do 'void *' (lub kwalifikowana wersja 'void *'). –

+0

@IanAbbott Czy znasz uzasadnienie standardu, aby wymagać tego pośredniego odlewania do 'void *'? –

Odpowiedz

5

Różnica istnieje również dlatego, że podczas gdy każdy wskaźnik do obiektu można przekształcić w void *, C nie wymaga wskaźniki funkcyjne mogą być konwertowane do void * iz powrotem!

Jeśli chodzi o wskaźniki do obiektów innych typów, standard C mówi, że każdy wskaźnik można przekonwertować na liczbę całkowitą, a liczbę całkowitą można przekonwertować na dowolny wskaźnik, ale takie wyniki są definiowane przez implementację. Ponieważ standard mówi, że tylko void * jest wymienialny iz powrotem, najbezpieczniej jest ustawić rzucając najpierw wskaźnik na void *, ponieważ może to wskazywać, że wskaźniki o różnych reprezentacjach po przekonwertowaniu na uintptr_t spowodują również inną reprezentację całkowitą, więc to może być możliwe, że:

int a; 
uintptr_t up = (uintptr_t)&a; 
void *p = (void *)up; 
p == &a; // could be conceivably false, or it might be even that the value is indeterminate. 
1

Tak, wymagana jest obsada do/z void *.

Sformułowanie tego fragmentu jest podobne do sformułowania specyfikatora formatu %p dla printf, który jako parametr wymagał parametru void *.

Z sekcji 7.19.6.1:

p argument powinien być wskaźnikiem do unieważnienia. Wartość wskaźnika jest przekształcana w sekwencję znaków drukarskich, w sposób określony przez implementację .

Można zrobić z powyższych zmiennych pośrednich mniej jednak:

int foo = 42; 
uintptr_t baz = (void *)&foo; 
int quux = *(int *)((void *)baz); /* quux == foo == 42 */ 
+0

Myślę, że powodem tego jest to, że implementacja nie zna typu oczekiwanego dla 'printf', ponieważ wywołujący widzi deklarację' printf' jako posiadającą '...'. Ale tutaj, podczas rzutowania kompilator wie, że 'void *' może zostać przekonwertowany. Tak więc możliwa jest niejawna obsada. –

1

Tak pośrednia konwersja do void* jest wymagane do tego, aby być przenośne. Wynika to z tego, że konwersja wskaźnika na liczbę całkowitą to "implementacja zdefiniowana", więc Twoja platforma może zrobić wszystko, o ile dokumentuje.

BTW, mieszasz "rzucanie" i "konwersję" w swoim pytaniu. "Konwersja" jest bardziej ogólnym określeniem, "obsada" to "wyraźna konwersja".

+0

Powód jest przede wszystkim dlatego, że 'uintptr_t' jest typu całkowitoliczbowego, co oznacza, że ​​bezpośredni wskaźnik do całkowitej liczby konwersji powoduje zdefiniowane zachowanie implementacji? –

+0

Jeśli "obsada" jest wyraźną okładką., Masz na myśli, że w pierwszym zdaniu wymagany jest rzut na 'void *'? –

+0

@AjayBrahmakshatriya, nie, mam na myśli to, co powiedziałem. Twoje pierwsze fragmenty kodu zawierają niejawne konwersje do 'void *', więc jest to w porządku. Twój drugi fragment ma tylko jawne konwersje na 'uintptr_t', a nie 'void *', więc nie jest dobrze. –

0

Jeśli mówimy o wskaźnikach w C, powinniśmy pomyśleć o tym, jak są reprezentowani. Zasadniczo rozmiar wartości wskaźnika zależy od architektury, a nie typu, na który wskazuje. Wartość void* i wartość uintptr_t jest taka sama, ale reprezentacja może się różnić. W przypadku uintptr_t według dokumentacji

są następujące typy wyznacza unsigned całkowitą z właściwością że każdy ważny wskaźnik do anulowania może być przekształcony w tego rodzaju następnie przekształcany z powrotem do wskaźnika do unieważnienia, a wynik porówna z równym oryginalnym wskaźnikiem: uintptr_t.

to oznaczać, że tego typu mogą być wykorzystane, aby bezpiecznie przechowywać wartość od wskaźnika i może być równy, większy lub mniejszy nawet wtedy void* sama z powodu realizacji konkretnych szczegółów.

Podsumowując, używając konwersji na void*, zapewniasz, że konwersja do/z uintptr_t jest ważna.