www tuto try tipos funcion dev casteo cast c++ c pointers casting

c++ - tuto - ¿Cuándo es realmente correcto un molde de puntero entero<->?



try on c++ (15)

El folclore común dice que:

  • El sistema de tipo existe por una razón. Los enteros y los punteros son tipos distintos, el lanzamiento entre ellos es una negligencia en la mayoría de los casos, puede indicar un error de diseño y debe evitarse.

  • Incluso cuando se realice un lanzamiento de este tipo, no se harán suposiciones sobre el tamaño de los enteros y punteros (la conversión de void* a int es la forma más sencilla de hacer que el código falle en x64), y en lugar de int se debe usar intptr_t o uintptr_t stdint.h .

Sabiendo eso, ¿ cuándo es realmente útil realizar tales lanzamientos?

(Nota: tener un código un poco más corto para el precio de la portabilidad no cuenta como "realmente útil").

Un caso que conozco:

  • Algunos algoritmos de multiprocesador sin bloqueos explotan el hecho de que un puntero con 2 bytes por bytes tiene alguna redundancia. Luego utilizan los bits más bajos del puntero como indicadores booleanos, por ejemplo. Con un procesador que tenga un conjunto de instrucciones apropiado, esto puede eliminar la necesidad de un mecanismo de bloqueo (que sería necesario si el puntero y el indicador booleano estuvieran separados).
    (Nota: Esta práctica incluso es posible hacer de forma segura en Java a través de java.util.concurrent.atomic.AtomicMarkableReference)

¿Algo más?


¿Cuándo es correcto almacenar punteros en ints? Es correcto cuando lo tratas como lo que es: el uso de una plataforma o comportamiento específico del compilador.

El problema es solo cuando tienes un código específico de plataforma / compilador en toda tu aplicación y tienes que transferir tu código a otra plataforma, porque has hecho suposiciones que ya no son ciertas. Al aislar ese código y esconderlo detrás de una interfaz que no hace suposiciones sobre la plataforma subyacente, usted elimina el problema.

Por lo tanto, siempre que documente la implementación, sepárela detrás de una interfaz independiente de la plataforma utilizando controladores o algo que no dependa de cómo funciona detrás de escena, y luego haga que el código se compile condicionalmente solo en plataformas / compiladores donde se haya probado y funciona, entonces no hay razón para que no uses ningún tipo de magia vudú que encuentres. Incluso puede incluir grandes cantidades de lenguaje ensamblador, llamadas API propietarias y llamadas al sistema kernel, si así lo desea.

Dicho esto, si su interfaz "portátil" usa identificadores enteros, los números enteros son del mismo tamaño que los punteros en la implementación para una determinada plataforma, y ​​esa implementación utiliza punteros internos, ¿por qué no simplemente usar los punteros como identificadores enteros? Un simple molde a un entero tiene sentido en ese caso, porque se corta la necesidad de una tabla de búsqueda de mango / puntero de algún tipo.


A veces lanzo punteros a números enteros cuando de alguna manera necesitan ser parte de un hashsum. También los evoco a enteros para hacer algunos trucos con ellos en ciertas implementaciones donde se garantiza que los punteros siempre tienen uno o dos bits de repuesto, donde puedo codificar AVL o RB información del árbol en los punteros izquierdo / derecho en lugar de tener un adicional miembro. Pero esto es tan específico para la implementación que recomiendo que nunca lo piense como cualquier tipo de solución común. También escuché que a veces los punteros de riesgo se pueden implementar con tal cosa.

En algunas situaciones, necesito una identificación única por cada objeto que paso, por ejemplo, a servidores como mi ID de solicitud. Dependiendo del contexto en el que necesite guardar algo de memoria y valga la pena, utilizo la dirección de mi objeto como tal, y generalmente tengo que convertirlo en un número entero.

Al trabajar con sistemas integrados (como en las cámaras canónicas, consulte chdk), a menudo hay anotaciones mágicas, por lo que a menudo también se encuentra un (void*)0xFFBC5235 o similar.

editar:

Acabo de tropezar (en mi mente) sobre pthread_self() que devuelve un pthread_t que generalmente es un typedef a un entero sin signo. Internamente, aunque es un puntero a alguna estructura de hilos, que representa el hilo en cuestión. En general, podría usarse en otro lugar para un mango opaco.


El caso más útil en mi mente es el que realmente tiene el potencial de hacer que los programas sean mucho más eficientes: una serie de interfaces de biblioteca estándar y comunes toman un solo argumento void * que pasarán de regreso a una función de devolución de llamada de algún tipo. Supongamos que su devolución de llamada no necesita gran cantidad de datos, solo un único argumento entero.

Si la devolución de llamada ocurrirá antes de que la función regrese, simplemente puede pasar la dirección de una variable int local (automática), y todo está bien. Pero el mejor ejemplo del mundo real para esta situación es pthread_create , donde la "devolución de llamada" se ejecuta en paralelo y no tiene garantía de que pueda leer el argumento a través del puntero antes de que devuelva pthread_create . En esta situación, tienes 3 opciones:

  1. malloc a single int y tener el nuevo hilo leído y free .
  2. Pase un puntero a una estructura local del llamante que contiene el int y un objeto de sincronización (por ejemplo, un semáforo o una barrera) y haga que el llamante lo espere después de llamar a pthread_create .
  3. Echa el int a void * y pásalo por valor.

La opción 3 es inmensamente más eficiente que cualquiera de las otras opciones, ambas implican un paso de sincronización adicional (para la opción 1, la sincronización está en malloc / free , y seguramente implicará algún costo ya que el hilo de asignación y liberación no es el mismo).


Es muy común en los sistemas integrados acceder a los dispositivos de hardware mapeados en memoria donde los registros están en direcciones fijas en el mapa de memoria. A menudo modelo hardware de forma diferente en C contra C ++ (con C ++ puede aprovechar las clases y plantillas), pero la idea general se puede utilizar para ambos.

Un ejemplo rápido: supongamos que tiene un periférico de temporizador en el hardware y tiene 2 registros de 32 bits:

  • un registro "tick count" de libre funcionamiento, que disminuye a una tasa fija (por ejemplo, cada microsegundo)

  • un registro de control, que le permite iniciar el temporizador, detener el temporizador, habilitar una interrupción de temporizador cuando disminuimos el conteo a cero, etc.

(Tenga en cuenta que un periférico de temporizador real suele ser significativamente más complicado).

Cada uno de estos registros son valores de 32 bits, y la "dirección base" del temporizador periférico es 0xFFFF.0000. Puede modelar el hardware de la siguiente manera:

// Treat these HW regs as volatile typedef uint32_t volatile hw_reg; // C friendly, hence the typedef typedef struct { hw_reg TimerCount; hw_reg TimerControl; } TIMER; // Cast the integer 0xFFFF0000 as being the base address of a timer peripheral. #define Timer1 ((TIMER *)0xFFFF0000) // Read the current timer tick value. // e.g. read the 32-bit value @ 0xFFFF.0000 uint32_t CurrentTicks = Timer1->TimerCount; // Stop / reset the timer. // e.g. write the value 0 to the 32-bit location @ 0xFFFF.0004 Timer1->TimerControl = 0;

Hay 100 variaciones en este enfoque, los pros y los contras de los cuales se pueden debatir para siempre, pero el punto aquí es solo para ilustrar un uso común de convertir un entero a un puntero. Tenga en cuenta que este código no es portátil, está vinculado a un dispositivo específico, supone que la región de memoria no está prohibida, etc.


Es posible que necesite acceder a la memoria en una dirección conocida y fija, luego su dirección será un número entero y deberá asignarla a un puntero. Esto es algo común en los sistemas integrados. Por el contrario, puede necesitar imprimir una dirección de memoria y, por lo tanto, debe convertirla en un número entero.

Ah, y no olvides que debes asignar y comparar punteros a NULL, que generalmente es un molde de puntero de 0L


Existe una vieja y buena tradición de usar un puntero a un objeto como un controlador sin tipo. Por ejemplo, algunas personas lo usan para implementar la interacción entre dos unidades de C ++ con API de estilo C plana. En ese caso, handle type se define como uno de los tipos enteros y cualquier método tiene que convertir un puntero en un entero antes de que pueda transferirse a otro método que espera un handle abstracto sin tipo como uno de sus parámetros. Además, a veces no hay otra forma de romper una dependencia circular.


He usado esos sistemas cuando estoy tratando de caminar byte a byte a través de una matriz. Muchas veces, el puntero caminará múltiples bytes a la vez, lo que causa problemas que son muy difíciles de diagnosticar.

Por ejemplo, int punteros:

int* my_pointer;

mover my_pointer++ dará como resultado el avance de 4 bytes (en un sistema estándar de 32 bits). Sin embargo, moviendo ((int)my_pointer)++ lo avanzará un byte.

En realidad, es la única forma de lograrlo, aparte de convertir el puntero en a (char *). ( (char*)my_pointer)++

Es cierto que el (char *) es mi método habitual, ya que tiene más sentido.


La única vez que lanzo un pointer a un integer es cuando quiero almacenar un puntero, pero el único almacenamiento que tengo disponible es un número entero.


Los valores del puntero también pueden ser una fuente útil de entropía para sembrar un generador de números aleatorios:

int* p = new int(); seed(intptr_t(p) ^ *p); delete p;

La biblioteca UUID boost utiliza este truco, y algunos otros.


Nunca es útil realizar tales lanzamientos, a menos que tenga un conocimiento completo del comportamiento de su compilación + combinación de plataforma, y ​​desee explotarlo (el escenario de su pregunta es uno de esos ejemplos).

La razón por la que digo que nunca es útil es porque, en general, no tiene control del compilador, ni conocimiento completo de las optimizaciones que puede elegir hacer. O para decirlo de otra manera, no puede controlar con precisión el código de máquina que generará. Entonces, en general, no puedes implementar este tipo de truco de forma segura.


Podría ser útil al verificar la alineación de los tipos en general para que la memoria desalineada quede atrapada con una afirmación en lugar de simplemente SIGBUS / SIGSEGV.

P.ej:

#include <xmmintrin.h> #include <assert.h> #include <stdint.h> int main() { void *ptr = malloc(sizeof(__m128)); assert(!((intptr_t)ptr) % __alignof__(__m128)); return 0; }

(En código real, no solo apostaría a malloc , sino que ilustra el punto)


Tengo un uso para tal cosa en identidades de objetos de toda la red. Tal identificación combinaría las identificaciones de la máquina (por ejemplo, la dirección IP), la identificación del proceso y la dirección del objeto. Para enviarse a través de un socket, la parte del puntero de dicha ID debe colocarse en un número suficientemente amplio como para que sobreviva al transporte de ida y vuelta. La parte del puntero solo se interpreta como un puntero (= regresa a un puntero) en el contexto donde esto tiene sentido (la misma máquina, el mismo proceso), en otras máquinas o en otros procesos solo sirve para distinguir diferentes objetos.

Las cosas que uno necesita para que funcione es la existencia de uintptr_t y uint64_t como un tipo entero de ancho de corrección. (Bueno, solo funciona en máquinas que tienen como máximo 64 direcciones :)


Un ejemplo es en Windows, por ejemplo, las funciones SendMessage() y PostMessage() . Toman un HWnd (un controlador para una ventana), un mensaje (un tipo integral) y dos parámetros para el mensaje, un WPARAM y un LPARAM . Ambos tipos de parámetros son integrales, pero a veces debe pasar punteros, según el mensaje que envíe. Luego deberá lanzar un puntero a un LPARAM o WPARAM .

En general, lo evitaría como la peste . Si necesita almacenar un puntero, use un tipo de puntero, si eso es posible.


en x64, on puede usar los bits superiores de los punteros para etiquetar (ya que solo se utilizan 47 bits para el puntero real). esto es ideal para cosas como la generación de código de tiempo de ejecución (LuaJIT usa esta técnica, que es una técnica antigua, de acuerdo con los comentarios), para hacer este etiquetado y comprobar que necesitas un yeso o una union , que básicamente equivalen a la misma cosa.

La fundición de punteros a enteros también puede ser muy útil en los sistemas de administración de memoria que hacen uso del binning, es decir: uno podría encontrar fácilmente el bin / página para una dirección a través de algunas matemáticas, un ejemplo de un asignador sin cerradura que escribí un tiempo espalda:

inline Page* GetPage(void* pMemory) { return &pPages[((UINT_PTR)pMemory - (UINT_PTR)pReserve) >> nPageShift]; }


Almacenar una lista doblemente vinculada usando la mitad del espacio

Una lista vinculada de XOR combina los punteros siguiente y anterior en un único valor del mismo tamaño. Hace esto combinando los dos punteros, lo que requiere tratarlos como enteros.