Konwersja wskaźnika na liczbę całkowitą

85

Próbuję dostosować istniejący kod do maszyny 64-bitowej. Główny problem polega na tym, że w jednej funkcji poprzedni koder używa argumentu void *, który jest konwertowany na odpowiedni typ w samej funkcji. Krótki przykład:

void function(MESSAGE_ID id, void* param)
{
    if(id == FOO) {
        int real_param = (int)param;
        // ...
    }
}

Oczywiście na komputerze 64-bitowym pojawia się błąd:

error: cast from 'void*' to 'int' loses precision

Chciałbym to poprawić, aby nadal działało na komputerze 32-bitowym i tak czysto, jak to możliwe. Dowolny pomysł ?

PierreBdR
źródło
5
Wiem, że to kopanie starego postu, ale wygląda na to, że zaakceptowana odpowiedź nie jest do końca poprawna. Konkretnym przykładem size_tniedziałającej pamięci jest segmentowana pamięć i386. Chociaż jest to maszyna 32-bitowa, sizeofwraca 2do size_t. Odpowiedź Alex poniżej wydaje się poprawna. Odpowiedź Alexa uintptr_tdziała prawie wszędzie i jest teraz standardem. Zapewnia obsługę C ++ 11, a nawet zapewnia ochronę nagłówka C ++ 03.
jww

Odpowiedzi:

68

Użyj intptr_ti uintptr_t.

Aby upewnić się, że jest zdefiniowany w przenośny sposób, możesz użyć takiego kodu:

#if defined(__BORLANDC__)
    typedef unsigned char uint8_t;
    typedef __int64 int64_t;
    typedef unsigned long uintptr_t;
#elif defined(_MSC_VER)
    typedef unsigned char uint8_t;
    typedef __int64 int64_t;
#else
    #include <stdint.h>
#endif

Po prostu umieść to w jakimś pliku .h i dołącz tam, gdzie tego potrzebujesz.

Alternatywnie możesz pobrać wersję stdint.hpliku firmy Microsoft stąd lub użyć wersji przenośnej stąd .

Milan Babuškov
źródło
Zobacz stackoverflow.com/questions/126279/…, aby uzyskać informacje o tym, jak uzyskać stdint.h, który działa z MSVC (i prawdopodobnie Borland).
Michael Burr
2
Oba łącza zerwane!
Antonio
1
Ta odpowiedź jest związana z C, ale język jest oznaczony jako C ++, więc nie jest to odpowiedź, której szukałem.
HaseeB Mir
@HaSeeBMiR Właściwą poprawką jest przełączenie się na <cstdint>lub pobranie odpowiedniego, cstdintjeśli pobierasz plik stdint.h.
Justin Time - Przywróć Monikę
1
@HaSeeBMiR Jedynym powodem, dla którego odpowiedź jest związana z C zamiast C ++, jest to, że używa nagłówka C zamiast odpowiadającego mu nagłówka C ++. Preprocesor C jest częścią C ++ i cstdintjest częścią standardu C ++, podobnie jak wszystkie zdefiniowane tam nazwy typów. Rzeczywiście jest to odpowiednie dla określonych tagów. ... Nie zgadzam się na ręczne definiowanie typów, ale może to być konieczne podczas pracy z kompilatorami, które tego nie robią.
Justin Time - Przywróć Monikę
91

Powiedziałbym, że to nowoczesny sposób C ++.

#include <cstdint>
void *p;
auto i = reinterpret_cast<std::uintptr_t>(p);

EDYCJA :

Prawidłowy typ do liczby całkowitej

więc właściwym sposobem na przechowywanie wskaźnika jako liczby całkowitej jest użycie typów uintptr_tlub intptr_t. (Zobacz także w typach całkowitych cppreference dla C99 ).

te typy są zdefiniowane w <stdint.h>C99 i w przestrzeni nazw stddla C ++ 11 w <cstdint>(zobacz typy całkowite dla C ++ ).

Wersja C ++ 11 (i nowsze)

#include <cstdint>
std::uintptr_t i;

Wersja C ++ 03

extern "C" {
#include <stdint.h>
}

uintptr_t i;

Wersja C99

#include <stdint.h>
uintptr_t i;

Właściwy operator rzutowania

W C jest tylko jedno rzutowanie i używanie rzutowania C w C ++ jest źle widziane (więc nie używaj go w C ++). W C ++ są różne rzuty. reinterpret_castjest prawidłową obsadą dla tej konwersji (zobacz także tutaj ).

Wersja C ++ 11

auto i = reinterpret_cast<std::uintptr_t>(p);

Wersja C ++ 03

uintptr_t i = reinterpret_cast<uintptr_t>(p);

Wersja C.

uintptr_t i = (uintptr_t)p; // C Version

Powiązane pytania

Alexander Oh
źródło
6
jedyna odpowiedź, która prawidłowo wymienia reinterpret_cast
plasmacel.
Jeśli zamierzałeś dołączyć <cstdint>, prawdopodobnie chcesz zamiast tego użyć std :: uintptr_t.
linleno
Niesamowite ... Obsada jest tym, czego szukałem. Jeśli mamy używać uintptr_tzamiast size_t, to dlaczego to wymaga reinterpret_cast? Wydaje się, że to proste static_cast, ponieważ standard wyraźnie zapewnia kompatybilne typy danych ...
jww
1
@jww przeczytaj: en.cppreference.com/w/cpp/language/static_cast Rozumiem, że static_castmoże to przekonwertować typ lub jeśli jest to wskaźnik, może dokonać korekty wskaźnika, jeśli typ tego wymaga. reinterpret_casttak naprawdę po prostu zmienia typ podstawowego wzorca pamięci (bez mutacji). wyjaśnienie: static_castzachowuje się tutaj identycznie.
Alexander Oh,
2
zamiast tego powinno być zaznaczone jako wybrana odpowiedź, ponieważ zawiera wszystkie szczegóły dotyczące rzutowania w C i C ++ .
HaseeB Mir
42

„size_t” i „ptrdiff_t” są wymagane, aby pasowały do ​​Twojej architektury (cokolwiek to jest). Dlatego myślę, że zamiast używać „int”, powinieneś móc użyć parametru „size_t”, który w systemie 64-bitowym powinien być typem 64-bitowym.

Ta dyskusja unsigned int vs size_t jest bardziej szczegółowa.

Richard Corden
źródło
34
Chociaż size_t jest zwykle wystarczająco duże, aby pomieścić wskaźnik, niekoniecznie tak jest. Byłoby lepiej zlokalizować nagłówek stdint.h (jeśli Twój kompilator jeszcze go nie ma) i użyć uintptr_t.
Michael Burr
3
Niestety jedynym ograniczeniem size_tjest to, że musi zawierać wynik dowolnego sizeof(). Niekoniecznie oznacza to 64 bity na x64. zobacz także
Antoine
3
size_t może bezpiecznie przechowywać wartość wskaźnika niebędącego członkiem. Zobacz en.cppreference.com/w/cpp/types/size_t .
AndyJost
2
@AndyJost Nie, nie może. Potwierdza to nawet Twój własny link.
yyny,
1
@YoYoYonnY: "Na wielu platformach (wyjątkiem są systemy z segmentowym adresowaniem) std :: size_t może bezpiecznie przechowywać wartość dowolnego wskaźnika niebędącego członkiem, w takim przypadku jest to synonim std :: uintptr_t." - o czym ty mówisz?
slashmais
16

Użyj uintptr_tjako typu liczby całkowitej.

księżycowy cień
źródło
8

Kilka odpowiedzi wskazywały na uintptr_ti #include <stdint.h>jako „” rozwiązania. To jest, jak sugeruję, część odpowiedzi, ale nie cała odpowiedź. Musisz również sprawdzić, gdzie wywoływana jest funkcja z identyfikatorem wiadomości FOO.

Rozważ ten kod i kompilację:

$ cat kk.c
#include <stdio.h>
static void function(int n, void *p)
{
    unsigned long z = *(unsigned long *)p;
    printf("%d - %lu\n", n, z);
}

int main(void)
{
    function(1, 2);
    return(0);
}
$ rmk kk
        gcc -m64 -g -O -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith \
            -Wcast-qual -Wstrict-prototypes -Wmissing-prototypes \
            -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE kk.c -o kk 
kk.c: In function 'main':
kk.c:10: warning: passing argument 2 of 'func' makes pointer from integer without a cast
$

Zauważysz, że występuje problem w miejscu wywołania (in main()) - konwersja liczby całkowitej na wskaźnik bez rzutowania. Będziesz musiał przeanalizować function()wszystkie jego zastosowania, aby zobaczyć, jak przekazywane są do niego wartości. Kod wewnątrz mojego function()działałby, gdyby wywołania były napisane:

unsigned long i = 0x2341;
function(1, &i);

Ponieważ twoje są prawdopodobnie napisane inaczej, musisz przejrzeć punkty, w których funkcja jest wywoływana, aby upewnić się, że ma sens użycie wartości, jak pokazano. Nie zapominaj, że możesz znaleźć ukryty błąd.

Ponadto, jeśli zamierzasz sformatować wartość void *parametru (jako przekonwertowaną), spójrz uważnie na <inttypes.h>nagłówek (zamiast stdint.h- inttypes.hzapewnia usługi stdint.h, co jest niezwykłe, ale standard C99 mówi, że [t] on nagłówek <inttypes.h>zawiera nagłówek <stdint.h>i rozszerza go o dodatkowe udogodnienia zapewniane przez hostowane implementacje ) i użyj makr PRIxxx w ciągach formatujących.

Ponadto moje komentarze odnoszą się ściśle do C, a nie C ++, ale Twój kod należy do podzbioru C ++, który jest przenośny między C i C ++. Są duże szanse, że moje uwagi będą miały zastosowanie.

Jonathan Leffler
źródło
3
Myślę, że przegapiłeś punkt w moim pytaniu. Kod przechowuje wartość liczby całkowitej we wskaźniku. Ta część kodu działa odwrotnie (np. Wyodrębnia wartość liczby całkowitej zapisanej jako wskaźnik).
PierreBdR
@PierreBdR Niemniej jednak zwraca uwagę na bardzo ważny punkt. Nie zawsze jest to takie proste, aby spojrzeć na kod (w tym, gdy kompilatory o nim ostrzegają), który używa podpisanego int, ale jest używany do rozmiaru i uważa, że ​​można go zmienić na unsigned. Niestety nie zawsze jest to takie proste. Musisz dokładnie przyjrzeć się każdemu przypadkowi, chyba że chcesz spowodować potencjalne błędy - i to subtelne.
Pryftan
4
  1. #include <stdint.h>
  2. Użyj uintptr_tstandardowego typu zdefiniowanego w dołączonym standardowym pliku nagłówkowym.
Michael S.
źródło
3

Natknąłem się na to pytanie, studiując kod źródłowy SQLite .

W sqliteInt.h znajduje się akapit kodu, w którym zdefiniowano makro konwertujące między liczbą całkowitą a wskaźnikiem. Autor złożył bardzo dobre oświadczenie, najpierw wskazując, że powinien to być problem zależny od kompilatora, a następnie zaimplementował rozwiązanie, aby uwzględnić większość popularnych kompilatorów.

#if defined(__PTRDIFF_TYPE__)  /* This case should work for GCC */
# define SQLITE_INT_TO_PTR(X)  ((void*)(__PTRDIFF_TYPE__)(X))
# define SQLITE_PTR_TO_INT(X)  ((int)(__PTRDIFF_TYPE__)(X))
#elif !defined(__GNUC__)       /* Works for compilers other than LLVM */
# define SQLITE_INT_TO_PTR(X)  ((void*)&((char*)0)[X])
# define SQLITE_PTR_TO_INT(X)  ((int)(((char*)X)-(char*)0))
#elif defined(HAVE_STDINT_H)   /* Use this case if we have ANSI headers */
# define SQLITE_INT_TO_PTR(X)  ((void*)(intptr_t)(X))
# define SQLITE_PTR_TO_INT(X)  ((int)(intptr_t)(X))
#else                          /* Generates a warning - but it always works     */
# define SQLITE_INT_TO_PTR(X)  ((void*)(X))
# define SQLITE_PTR_TO_INT(X)  ((int)(X))
#endif

A oto cytat z komentarza po więcej szczegółów:

/*
** The following macros are used to cast pointers to integers and
** integers to pointers.  The way you do this varies from one compiler
** to the next, so we have developed the following set of #if statements
** to generate appropriate macros for a wide range of compilers.
**
** The correct "ANSI" way to do this is to use the intptr_t type.
** Unfortunately, that typedef is not available on all compilers, or
** if it is available, it requires an #include of specific headers
** that vary from one machine to the next.
**
** Ticket #3860:  The llvm-gcc-4.2 compiler from Apple chokes on
** the ((void*)&((char*)0)[X]) construct.  But MSVC chokes on ((void*)(X)).
** So we have to define the macros in different ways depending on the
** compiler.
*/

Kredyt trafia do zatwierdzających.

B.Mr.W.
źródło
2

Najlepszą rzeczą do zrobienia jest unikanie konwersji z typu wskaźnikowego na typy niebędące wskaźnikami. Jednak w twoim przypadku jest to oczywiście niemożliwe.

Jak wszyscy powiedzieli, uintptr_t jest tym, czego powinieneś używać.

To łącze zawiera dobre informacje na temat konwersji do kodu 64-bitowego.

Jest to również dobre omówienie w comp.std.c

Benoit
źródło
2

Myślę, że „znaczenie” void * w tym przypadku jest ogólnym uchwytem. Nie jest wskaźnikiem do wartości, jest wartością samą w sobie. (Tak się składa, że ​​void * jest używany przez programistów C i C ++).

Jeśli przechowuje wartość całkowitą, lepiej, aby mieścił się w zakresie liczb całkowitych!

Oto łatwe renderowanie do liczby całkowitej:

int x = (char*)p - (char*)0;

Powinien tylko dawać ostrzeżenie.

Craig Hicks
źródło
0

Ponieważ nieuintptr_t ma gwarancji, że będzie tam w C ++ / C ++ 11 , jeśli jest to konwersja jednokierunkowa, którą możesz rozważyć uintmax_t, zawsze zdefiniowana w <cstdint>.

auto real_param = reinterpret_cast<uintmax_t>(param);

Aby być bezpiecznym, można dodać w dowolnym miejscu kodu asercję:

static_assert(sizeof (uintmax_t) >= sizeof (void *) ,
              "No suitable integer type for conversion from pointer type");
Antonio
źródło
Jeśli nie masz uintptr_t, to uintmax_t też nie jest odpowiedzią: nie ma gwarancji, że możesz w nim przechowywać wartość wskaźnika! Może nie istnieć typ liczb całkowitych, który to robi.
PierreBdR