Conversione di un puntatore in un numero intero


88

Sto cercando di adattare un codice esistente a una macchina a 64 bit. Il problema principale è che in una funzione, il programmatore precedente utilizza un argomento void * che viene convertito in un tipo adatto nella funzione stessa. Un breve esempio:

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

Ovviamente, su una macchina a 64 bit, ottengo l'errore:

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

Vorrei correggere questo in modo che funzioni ancora su una macchina a 32 bit e nel modo più pulito possibile. Qualche idea ?


5
So che questo sta scavando un vecchio post, ma sembra che la risposta accettata non sia del tutto corretta. Un esempio concreto di size_tnon funzionamento è la memoria segmentata i386. Sebbene sia una macchina a 32 bit, sizeofritorna 2per size_t. La risposta di Alex di seguito sembra corretta. La risposta di Alex e uintptr_tfunziona praticamente ovunque ed è ora standard. Fornisce un trattamento C ++ 11 e fornisce anche le protezioni per le intestazioni C ++ 03.
jww

Risposte:


70

Usa intptr_te uintptr_t.

Per assicurarti che sia definito in modo portatile, puoi usare codice come questo:

#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

Inseriscilo in un file .h e includilo ovunque ti serva.

In alternativa, puoi scaricare la versione Microsoft del stdint.hfile da qui o utilizzarne uno portatile da qui .


Vedi stackoverflow.com/questions/126279/… per informazioni su come ottenere un stdint.h che funzioni con MSVC (e possibilmente Borland).
Michael Burr

2
Entrambi i collegamenti sono interrotti!
Antonio

1
Questa risposta è correlata al C ma il linguaggio è etichettato C ++, quindi non è la risposta che stavo cercando.
HaseeB Mir

@HaSeeBMiR Una soluzione appropriata è passare a <cstdint>, o scaricare il file appropriato cstdintse si scarica un file stdint.h.
Justin Time - Ripristina Monica il

1
@HaSeeBMiR L'unico motivo per cui la risposta è correlata a C invece che a C ++ è che utilizza un'intestazione C invece dell'equivalente intestazione C ++. Il preprocessore C è una parte di C ++ ed cstdintè una parte dello standard C ++, come lo sono tutti i nomi di tipo qui definiti. È effettivamente appropriato per i tag specificati. ... Non sono d'accordo con la definizione manuale dei tipi, ma potrebbe essere necessario quando si lavora con compilatori che non lo fanno.
Justin Time - Ripristina Monica il

94

Direi che questo è il modo moderno del C ++.

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

MODIFICA :

Il tipo corretto in Integer

quindi il modo giusto per memorizzare un puntatore come numero intero è usare i tipi uintptr_to intptr_t. (Vedi anche in cppreference tipi interi per C99 ).

questi tipi sono definiti in <stdint.h>per C99 e nello spazio dei nomi stdper C ++ 11 in <cstdint>(vedere tipi interi per C ++ ).

Versione C ++ 11 (e successive)

#include <cstdint>
std::uintptr_t i;

Versione C ++ 03

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

uintptr_t i;

Versione C99

#include <stdint.h>
uintptr_t i;

L'operatore di casting corretto

In C c'è un solo cast e l'uso del cast C in C ++ è disapprovato (quindi non usarlo in C ++). In C ++ ci sono diversi cast. reinterpret_castè il cast corretto per questa conversione (vedi anche qui ).

Versione C ++ 11

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

Versione C ++ 03

uintptr_t i = reinterpret_cast<uintptr_t>(p);

Versione C.

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

domande correlate


6
l'unica risposta che menziona correttamente reinterpret_cast
plasmacel

Se intendevi includere <cstdint>, probabilmente vorrai usare anche std :: uintptr_t.
linleno

Fantastico ... Il cast è quello che stavo cercando. Se ci viene detto di usare uintptr_tinvece di size_t, perché lo richiede reinterpret_cast? Sembra una cosa semplice da static_castfare poiché lo standard fornisce specificamente i tipi di dati compatibili ...
jww

1
@jww leggi: en.cppreference.com/w/cpp/language/static_cast qui la mia comprensione è che static_castpotrebbe convertire il tipo o, se è un puntatore, potrebbe apportare modifiche al puntatore se il tipo ne ha bisogno. reinterpret_castin realtà sta solo cambiando il tipo di pattern di memoria sottostante (senza mutazioni). per chiarire: static_castqui si comporta in modo identico.
Alexander Oh

2
questa dovrebbe invece essere contrassegnata come risposta selezionata, poiché fornisce tutti i dettagli su come eseguire il cast in C e C ++ .
HaseeB Mir

43

'size_t' e 'ptrdiff_t' sono necessari per abbinare la tua architettura (qualunque essa sia). Pertanto, penso che invece di usare 'int', dovresti essere in grado di usare 'size_t', che su un sistema a 64 bit dovrebbe essere un tipo a 64 bit.

Questa discussione unsigned int vs size_t entra un po 'più in dettaglio.


34
Sebbene size_t sia solitamente abbastanza grande da contenere un puntatore, non è necessariamente così. Sarebbe meglio individuare un'intestazione stdint.h (se il compilatore non ne ha già uno) e utilizzare uintptr_t.
Michael Burr il

3
Purtroppo l'unico vincolo size_tè che deve contenere il risultato di qualsiasi sizeof(). Questo non rende necessariamente 64 bit su x64. vedi anche
Antoine

3
size_t può memorizzare in modo sicuro il valore di un puntatore non membro. Vedi en.cppreference.com/w/cpp/types/size_t .
AndyJost

2
@ AndyJost No, non può. Anche il tuo link lo conferma.
yyny

1
@YoYoYonnY: "Su molte piattaforme (un'eccezione sono i sistemi con indirizzamento segmentato) std :: size_t può memorizzare in modo sicuro il valore di qualsiasi puntatore non membro, nel qual caso è sinonimo di std :: uintptr_t." - di cosa stai parlando?
slashmais


8

Diverse risposte hanno indicato uintptr_te #include <stdint.h>come "la" soluzione. Cioè, suggerisco, parte della risposta, ma non l'intera risposta. È inoltre necessario esaminare dove viene chiamata la funzione con l'ID messaggio di FOO.

Considera questo codice e la compilazione:

$ 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
$

Noterai che c'è un problema nella posizione di chiamata (in main()) - convertire un intero in un puntatore senza cast. Avrai bisogno di analizzare il tuo function()in tutti i suoi usi per vedere come gli vengono passati i valori. Il codice all'interno del mio function()funzionerebbe se le chiamate fossero scritte:

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

Poiché i tuoi sono probabilmente scritti in modo diverso, devi rivedere i punti in cui viene chiamata la funzione per assicurarti che abbia senso usare il valore come mostrato. Non dimenticare che potresti trovare un bug latente.

Inoltre, se intendi formattare il valore del void *parametro (come convertito), guarda attentamente l' <inttypes.h>intestazione (invece di stdint.h- inttypes.hfornisce i servizi di stdint.h, il che è insolito, ma lo standard C99 dice [t] che l'intestazione <inttypes.h>include l'intestazione <stdint.h>e lo estende con funzionalità aggiuntive fornite dalle implementazioni ospitate ) e utilizza le macro PRIxxx nelle stringhe di formato.

Inoltre, i miei commenti sono strettamente applicabili a C piuttosto che a C ++, ma il tuo codice è nel sottoinsieme di C ++ che è portabile tra C e C ++. Ci sono buone possibilità che i miei commenti siano validi.


3
Penso che tu abbia perso il punto della mia domanda. Il codice sta memorizzando il valore di un intero in un puntatore. E quella parte del codice sta facendo l'opposto (ad esempio estraendo il valore dell'intero che è stato scritto come puntatore).
PierreBdR

@ PierreBdR Tuttavia fa un punto molto valido. Non è sempre così semplice come guardare il codice (anche quando i compilatori lo avvertono) che usa un int firmato ma è usato per una dimensione e pensa che sia giusto cambiarlo in unsigned. Purtroppo non è sempre così semplice. Devi guardare ogni caso esplicitamente a meno che tu non voglia causare potenziali bug - e bug sottili a questo.
Pryftan

4
  1. #include <stdint.h>
  2. Usa il uintptr_ttipo standard definito nel file di intestazione standard incluso.

4

Mi sono imbattuto in questa domanda mentre studiavo il codice sorgente di SQLite .

In sqliteInt.h , c'è un paragrafo di codice definito una macro convertita tra intero e puntatore. L'autore ha fatto una dichiarazione molto buona prima sottolineando che dovrebbe essere un problema dipendente dal compilatore e poi ha implementato la soluzione per tenere conto della maggior parte dei compilatori popolari là fuori.

#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

Ed ecco una citazione del commento per maggiori dettagli:

/*
** 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.
*/

Il merito va ai committer.


2

La cosa migliore da fare è evitare la conversione dal tipo puntatore a tipi non puntatore. Tuttavia, questo chiaramente non è possibile nel tuo caso.

Come tutti hanno detto, uintptr_t è ciò che dovresti usare.

Questo collegamento contiene buone informazioni sulla conversione in codice a 64 bit.

C'è anche una buona discussione su questo su comp.std.c


2

Penso che il "significato" di void * in questo caso sia un handle generico. Non è un puntatore a un valore, è il valore stesso. (Questo è solo il modo in cui void * viene utilizzato dai programmatori C e C ++.)

Se contiene un valore intero, è meglio che sia compreso nell'intervallo intero!

Ecco un semplice rendering in intero:

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

Dovrebbe solo dare un avvertimento.


0

Poiché nonuintptr_t è garantito che sia presente in C ++ / C ++ 11 , se questa è una conversione unidirezionale che puoi considerare uintmax_t, sempre definita in <cstdint>.

auto real_param = reinterpret_cast<uintmax_t>(param);

Per andare sul sicuro, si potrebbe aggiungere in qualsiasi punto del codice un'asserzione:

static_assert(sizeof (uintmax_t) >= sizeof (void *) ,
              "No suitable integer type for conversion from pointer type");

Se non hai uintptr_t, anche uintmax_t non è una risposta: non c'è alcuna garanzia che tu possa memorizzare il valore di un puntatore in esso! Potrebbe non esserci alcun tipo intero che lo fa.
PierreBdR
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.