¿Qué es el tipo de datos uintptr_t?

Respuestas:

199

uintptr_tes un tipo entero sin signo que es capaz de almacenar un puntero de datos. Lo que generalmente significa que tiene el mismo tamaño que un puntero.

Opcionalmente se define en C ++ 11 y estándares posteriores.

Una razón común para querer un tipo entero que pueda contener el tipo de puntero de una arquitectura es realizar operaciones específicas de un entero en un puntero u ocultar el tipo de puntero proporcionándolo como un "identificador" entero.

Editar: Tenga en cuenta que Steve Jessop tiene algunos detalles adicionales muy interesantes (que no voy a robar) en otra respuesta aquí para sus tipos pedantes :)

Drew Dormann
fuente
53
Tenga en cuenta que size_tsolo necesita ser suficiente para contener el tamaño del objeto más grande y puede ser más pequeño que un puntero. Esto se esperaría en arquitecturas segmentadas como el 8086 (16 bits size_t, pero 32 bits void*)
MSalters
3
para representar la diferencia entre dos punteros, tienes ptrdiff_t. uintptr_tno es para eso.
jalf
3
@jalf: Por diferencia, sí, pero para la distancia , querrás un tipo sin signo.
Drew Dormann el
¡La definición de uintptr_t aparentemente no es obligatoria (por lo que no es estándar) incluso en C ++ 11! cplusplus.com/reference/cstdint (Recibí la pista de la respuesta de Steve Jessop)
Antonio
2
@MarcusJ unsigned intgeneralmente no es lo suficientemente grande. Pero podría ser lo suficientemente grande. Este tipo existe específicamente para eliminar todos los "supuestos" .
Drew Dormann
239

Lo primero, cuando se hizo la pregunta, uintptr_tno estaba en C ++. Está en C99, en <stdint.h>, como un tipo opcional. Muchos compiladores de C ++ 03 proporcionan ese archivo. También está en C ++ 11, en <cstdint>, donde nuevamente es opcional, y que se refiere a C99 para la definición.

En C99, se define como "un tipo entero sin signo con la propiedad de que cualquier puntero válido a vacío se puede convertir a este tipo, luego se convierte de nuevo a puntero a vacío, y el resultado se comparará igual al puntero original".

Tome esto para significar lo que dice. No dice nada sobre el tamaño.

uintptr_tpodría ser del mismo tamaño que a void*. Podría ser más grande. Posiblemente podría ser más pequeño, aunque tal implementación de C ++ se aproxima a perversa. Por ejemplo, en alguna plataforma hipotética donde void*hay 32 bits, pero solo se usan 24 bits de espacio de direcciones virtuales, podría tener un bit de 24 bits uintptr_tque satisfaga el requisito. No sé por qué una implementación haría eso, pero el estándar lo permite.

Steve Jessop
fuente
8
Gracias por el "<stdint.h>". Mi aplicación no se compiló debido a la declaración uintptr_t. Pero cuando leo su comentario agrego "#include <stdint.h>" y sí, ahora funciona. ¡Gracias!
JavaRunner
2
¿Para asignar memoria alineada , entre otros usos, por ejemplo?
legends2k
44
Otro caso de uso común de lanzar un puntero a un int es crear un controlador opaco que oculte el puntero. Esto es útil para devolver una referencia a un objeto desde las API donde desea mantener el objeto privado en la biblioteca y evitar que las aplicaciones tengan acceso a él. La aplicación se ve obligada a usar la API para realizar cualquier operación en el objeto
Joel Cunningham
3
@JoelCunningham: eso funciona pero en realidad no es diferente del uso void*. Sin embargo, afecta las posibles direcciones futuras, especialmente si es posible que desee cambiar para usar algo que realmente es solo un identificador entero, no un puntero convertido en absoluto.
Steve Jessop
2
@CiroSantilli 烏坎 事件 2016 六四 事件 法轮功 Un caso de uso común es pasar solo un int a una API que espera un vacío * a los datos genéricos. Te ahorra las pulsaciones del teclado typedef struct { int whyAmIDoingThis; } SeriouslyTooLong; SeriouslyTooLong whyAmNotDoneYet; whyAmINotDoneYet.whyAmIDoingThis = val; callback.dataPtr = &whyAmINotDoneYet;. En su lugar: callback.dataPtr = (void*)val. Por otro lado, por supuesto, obtienes void*y tienes que volver a lanzarlo int.
Francesco Dondi
19

Es un tipo entero sin signo exactamente del tamaño de un puntero. Siempre que necesite hacer algo inusual con un puntero, como por ejemplo invertir todos los bits (no pregunte por qué), lo emite uintptr_ty lo manipula como un número entero habitual, luego lo devuelve.

diente filoso
fuente
66
Por supuesto que podrías hacer eso, pero eso sería un comportamiento indefinido. Creo que lo único que puedes hacer con el resultado de un lanzamiento a uintptr_t es pasarlo sin cambios y devolverlo; todo lo demás es UB.
sleske
Hay momentos en los que necesitas jugar con los bits, y normalmente generaría errores de compilación. Un ejemplo común es aplicar memoria alineada de 16 bytes para ciertas aplicaciones críticas de video y rendimiento. stackoverflow.com/questions/227897/…
Cloud
3
@sleske eso no es cierto. En máquinas que tienen tipos autoalineados, los dos bits menos significativos de un puntero serán cero (porque las direcciones son múltiplos de 4 u 8). He visto programas que explotan esto para comprimir datos ...
saadtaame
3
@saadtaame: acabo de señalar que se trata de UB acuerdo con la norma C . Eso no significa que no se pueda definir en algunos sistemas: los compiladores y los tiempos de ejecución son libres de definir un comportamiento específico para algo que es UB en el estándar C. Por lo tanto, no hay contradicción :-).
sleske
2
No es necesariamente exactamente el tamaño de un puntero. Todas las garantías estándar son que la conversión de un void*valor de puntero a uintptr_ty de nuevo produce un void*valor que se compara igual al puntero original. uintptr_tusualmente es del mismo tamaño que void*, pero eso no está garantizado, ni hay ninguna garantía de que los bits del valor convertido tengan un significado particular. Y no hay garantía de que pueda contener un valor convertido de puntero a función sin pérdida de información. Finalmente, no se garantiza que exista.
Keith Thompson
15

Ya hay muchas buenas respuestas a la parte "qué es el tipo de datos uintptr_t". Intentaré abordar el tema "¿para qué se puede utilizar?" parte en esta publicación.

Principalmente para operaciones bit a bit en punteros. Recuerde que en C ++ no se pueden realizar operaciones bit a bit en punteros. Por razones, consulte ¿Por qué no puede realizar operaciones bit a bit en el puntero en C? ¿Hay alguna forma de evitar esto?

Por lo tanto, para realizar operaciones bit a bit en punteros, sería necesario lanzar punteros para escribir unitpr_t y luego realizar operaciones bit a bit.

Aquí hay un ejemplo de una función que acabo de escribir para hacer bitwise exclusivo o de 2 punteros para almacenar en una lista vinculada XOR para que podamos atravesar en ambas direcciones como una lista doblemente vinculada pero sin la penalidad de almacenar 2 punteros en cada nodo .

 template <typename T>
 T* xor_ptrs(T* t1, T* t2)
 {
     return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(t1)^reinterpret_cast<uintptr_t>(t2));
  }
BGR
fuente
1
aparte de la aritmética de bits, también es bueno si desea tener una semántica basada en direcciones en lugar de recuentos de objetos.
Alex
4

Corriendo el riesgo de obtener otra insignia de Nigromante, me gustaría agregar un uso muy bueno para uintptr_t (o incluso intptr_t) y eso es escribir código incrustable comprobable. Escribo principalmente código incrustado dirigido a varios procesadores de brazo y actualmente tensilica. Estos tienen varios anchos de bus nativos y la tensilica es en realidad una arquitectura de Harvard con código separado y buses de datos que pueden tener diferentes anchos. Utilizo un estilo de desarrollo basado en pruebas para gran parte de mi código, lo que significa que hago pruebas unitarias para todas las unidades de código que escribo. Las pruebas unitarias en el hardware de destino real son una molestia, por lo que normalmente escribo todo en una PC basada en Intel, ya sea en Windows o Linux usando Ceedling y GCC. Dicho esto, una gran cantidad de código incrustado implica modificaciones de bits y manipulaciones de direcciones. La mayoría de mis máquinas Intel son de 64 bits. Entonces, si va a probar el código de manipulación de dirección, necesita un objeto generalizado para hacer matemáticas. Por lo tanto, uintptr_t le brinda una forma independiente de la máquina de depurar su código antes de intentar implementarlo en el hardware de destino. Otro problema es para algunas máquinas o incluso modelos de memoria en algunos compiladores, los punteros de función y los punteros de datos tienen diferentes anchos. En esas máquinas, es posible que el compilador ni siquiera permita la conversión entre las dos clases, pero uintptr_t debería ser capaz de contener cualquiera.

bd2357
fuente
No estoy seguro si es relevante ... ¿Has probado "opaque typedefs"? Vide: youtube.com/watch?v=jLdSjh8oqmE
shycha
@sleske Desearía que estuviera disponible en C. Pero tener stdint.h es mejor que nada. (También desearía enumeraciones donde se escriban con más fuerza, pero la mayoría de los depuradores hacen un buen trabajo al resolverlas de cualquier manera)
bd2357