Convertir un puntero en un número entero

85

Estoy tratando de adaptar un código existente a una máquina de 64 bits. El principal problema es que en una función, el codificador anterior usa un argumento void * que se convierte al tipo adecuado en la función misma. Un pequeño ejemplo:

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

Por supuesto, en una máquina de 64 bits, aparece el error:

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

Me gustaría corregir esto para que todavía funcione en una máquina de 32 bits y lo más limpiamente posible. Alguna idea ?

PierreBdR
fuente
5
Sé que esto es desenterrar una publicación antigua, pero parece que la respuesta aceptada no es del todo correcta. Un ejemplo concreto de que size_tno funciona es la memoria segmentada i386. Aunque una máquina de 32 bits, sizeofdevuelve 2a size_t. La respuesta de Alex a continuación parece correcta. Alex responde y uintptr_tfunciona en casi todas partes y ahora es estándar. Proporciona un tratamiento de C ++ 11 e incluso proporciona protecciones de encabezado de C ++ 03.
jww

Respuestas:

68

Utilice intptr_ty uintptr_t.

Para asegurarse de que esté definido de forma portátil, puede utilizar un código como este:

#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

Simplemente colóquelo en algún archivo .hy inclúyalo donde lo necesite.

Alternativamente, puede descargar la versión de Microsoft del stdint.harchivo desde aquí o usar una portátil desde aquí .

Milán Babuškov
fuente
Consulte stackoverflow.com/questions/126279/… para obtener información sobre cómo obtener un stdint.h que funcione con MSVC (y posiblemente con Borland).
Michael Burr
2
¡Ambos enlaces rotos!
Antonio
1
Esta respuesta está relacionada con C, pero el lenguaje está etiquetado como C ++, por lo que no es la respuesta que estaba buscando.
HaseeB Mir
@HaSeeBMiR Una solución adecuada es cambiar <cstdint>o descargar el archivo apropiado cstdintsi descarga un archivo stdint.h.
Justin Time - Reincorpora a Monica
1
@HaSeeBMiR La única razón por la que la respuesta está relacionada con C en lugar de C ++ es que usa un encabezado C en lugar del encabezado C ++ equivalente. El preprocesador de C es parte de C ++ y cstdintes parte del estándar C ++, al igual que todos los nombres de tipo definidos allí. De hecho, es apropiado para las etiquetas especificadas. ... No estoy de acuerdo con definir los tipos manualmente, pero puede ser necesario cuando se trabaja con compiladores que no lo hacen.
Justin Time - Reincorporación a Monica
91

Yo diría que esta es la forma moderna de C ++.

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

EDITAR :

El tipo correcto para el entero

por lo que la forma correcta de almacenar un puntero como un entero es usar los tipos uintptr_to intptr_t. (Ver también en cppreference tipos de enteros para C99 ).

estos tipos se definen en <stdint.h>para C99 y en el espacio stdde nombres para C ++ 11 en <cstdint>(consulte los tipos de enteros para C ++ ).

Versión C ++ 11 (y posteriores)

#include <cstdint>
std::uintptr_t i;

Versión C ++ 03

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

uintptr_t i;

Versión C99

#include <stdint.h>
uintptr_t i;

El operador de fundición correcto

En C solo hay un elenco y el uso del elenco de C en C ++ está mal visto (así que no lo use en C ++). En C ++ hay diferentes moldes. reinterpret_castes el elenco correcto para esta conversión (ver también aquí ).

Versión C ++ 11

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

Versión C ++ 03

uintptr_t i = reinterpret_cast<uintptr_t>(p);

Versión C

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

preguntas relacionadas

Alejandro Oh
fuente
6
la única respuesta que menciona correctamente reinterpret_cast
plasmacel
Si deseaba incluir <cstdint>, probablemente también desee utilizar std :: uintptr_t en su lugar.
linleno
Impresionante ... El elenco es lo que estaba buscando. Si se nos dice que usemos en uintptr_tlugar de size_t, ¿por qué lo requiere reinterpret_cast? Parece algo sencillo static_cast, ya que el estándar proporciona específicamente los tipos de datos compatibles ...
jww
1
@jww leer: en.cppreference.com/w/cpp/language/static_cast Mi entendimiento aquí es que static_castpodría convertir el tipo o si es un puntero podría hacer ajustes de puntero si el tipo lo necesita. reinterpret_casten realidad, solo está cambiando el tipo de patrón de memoria subyacente (sin mutaciones). para aclarar: static_castse comporta idéntico aquí.
Alexander Oh
2
en su lugar, debe marcarse como respuesta seleccionada, ya que proporciona todos los detalles sobre cómo convertir en C y C ++ .
HaseeB Mir
42

'size_t' y 'ptrdiff_t' son necesarios para coincidir con su arquitectura (sea la que sea). Por lo tanto, creo que en lugar de usar 'int', debería poder usar 'size_t', que en un sistema de 64 bits debería ser del tipo de 64 bits.

Esta discusión unsigned int vs size_t entra en un poco más de detalle.

Richard Corden
fuente
34
Si bien size_t suele ser lo suficientemente grande como para contener un puntero, no es necesariamente el caso. Sería mejor ubicar un encabezado stdint.h (si su compilador aún no tiene uno) y usar uintptr_t.
Michael Burr
3
Desafortunadamente, la única restricción size_tes que debe contener el resultado de cualquiera sizeof(). Esto no necesariamente lo convierte en 64 bits en x64. ver también
Antoine
3
size_t puede almacenar de forma segura el valor de un puntero que no es miembro. Consulte en.cppreference.com/w/cpp/types/size_t .
AndyJost
2
@AndyJost No, no puede. Incluso su propio enlace lo confirma.
yyny
1
@YoYoYonnY: "En muchas plataformas (una excepción son los sistemas con direccionamiento segmentado) std :: size_t puede almacenar de forma segura el valor de cualquier puntero no miembro, en cuyo caso es sinónimo de std :: uintptr_t". - ¿De qué estás hablando?
slashmais
16

Úselo uintptr_tcomo su tipo de entero.

sombra de Luna
fuente
8

Varias respuestas han señalado uintptr_ty #include <stdint.h>como "la" solución. Eso es, sugiero, parte de la respuesta, pero no toda la respuesta. También debe observar dónde se llama a la función con el ID de mensaje de FOO.

Considere este código y compilación:

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

Observará que hay un problema en la ubicación de la llamada (en main()): convertir un entero en un puntero sin conversión. Vas a necesitar analizar tu function()en todos sus usos para ver cómo se le pasan los valores. El código dentro de mi function()funcionaría si las llamadas estuvieran escritas:

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

Dado que el suyo probablemente esté escrito de manera diferente, debe revisar los puntos donde se llama a la función para asegurarse de que tenga sentido usar el valor como se muestra. No lo olvide, puede estar encontrando un error latente.

Además, si va a formatear el valor del void *parámetro (como convertido), mire detenidamente el <inttypes.h>encabezado (en lugar de stdint.h- inttypes.hproporciona los servicios de stdint.h, lo cual es inusual, pero el estándar C99 dice que [t] el encabezado <inttypes.h>incluye el encabezado <stdint.h>y lo extiende con facilidades adicionales proporcionadas por implementaciones alojadas ) y use las macros PRIxxx en sus cadenas de formato.

Además, mis comentarios son estrictamente aplicables a C en lugar de C ++, pero su código está en el subconjunto de C ++ que es portátil entre C y C ++. Las posibilidades de que se apliquen mis comentarios son justas a buenas.

Jonathan Leffler
fuente
3
Creo que no entendiste mi pregunta. El código almacena el valor de un número entero en un puntero. Y esa parte del código está haciendo lo contrario (por ejemplo, extrayendo el valor del entero que se escribió como puntero).
PierreBdR
@PierreBdR Sin embargo, hace un punto muy válido. No siempre es tan simple como mirar el código (incluso cuando los compiladores advierten sobre él) que usa un int firmado pero se usa para un tamaño y cree que está bien cambiarlo a unsigned. Desafortunadamente, no siempre es tan simple. Debe observar cada caso de manera explícita a menos que desee causar errores potenciales, y errores sutiles en eso.
Pryftan
4
  1. #include <stdint.h>
  2. Utilice uintptr_tel tipo estándar definido en el archivo de encabezado estándar incluido.
Michael S.
fuente
3

Me encontré con esta pregunta mientras estudiaba el código fuente de SQLite .

En sqliteInt.h , hay un párrafo de código que define una conversión macro entre entero y puntero. El autor hizo una muy buena declaración primero señalando que debería ser un problema dependiente del compilador y luego implementó la solución para dar cuenta de la mayoría de los compiladores populares que existen.

#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

Y aquí hay una cita del comentario para más detalles:

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

El crédito es para los comprometidos.

B.Mr.W.
fuente
2

Lo mejor que puede hacer es evitar la conversión de tipos de puntero a tipos que no son de puntero. Sin embargo, esto claramente no es posible en su caso.

Como todos dijeron, el uintptr_t es lo que debe usar.

Este enlace tiene buena información sobre la conversión a código de 64 bits.

También hay una buena discusión sobre esto en comp.std.c

Benoit
fuente
2

Creo que el "significado" de void * en este caso es un identificador genérico. No es un puntero a un valor, es el valor mismo. (Esta es la forma en que los programadores de C y C ++ usan void *).

Si tiene un valor entero, ¡es mejor que esté dentro del rango de números enteros!

Aquí hay una representación fácil a un número entero:

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

Solo debería dar una advertencia.

Craig Hicks
fuente
0

Dado uintptr_tque no se garantiza que esté allí en C ++ / C ++ 11 , si se trata de una conversión unidireccional, puede considerar uintmax_t, siempre definida en <cstdint>.

auto real_param = reinterpret_cast<uintmax_t>(param);

Para jugar seguro, uno podría agregar en cualquier lugar del código una afirmación:

static_assert(sizeof (uintmax_t) >= sizeof (void *) ,
              "No suitable integer type for conversion from pointer type");
Antonio
fuente
Si no tiene uintptr_t, entonces uintmax_t tampoco es una respuesta: ¡no hay garantía de que pueda almacenar el valor de un puntero en él! Puede que no haya ningún tipo de entero que haga eso.
PierreBdR