puntero - convertir un numero entero a binario en c
¿Es siempre seguro convertir un valor entero a void*y volver a ponerlo en POSIX? (5)
(u) intptr_t solo se garantiza que sea lo suficientemente grande como para contener un puntero, pero también pueden ser "más grandes", por lo que el estándar C99 solo garantiza (void *) -> (u) intptr_t -> (void *), pero en el otro caso puede ocurrir una pérdida de datos (y se considera indefinida).
Esta pregunta es casi un duplicado de algunos otros que he encontrado, pero esto se refiere específicamente a POSIX, y es un ejemplo muy común en pthreads que he encontrado varias veces. Estoy más preocupado por el estado actual de las cosas (es decir, C99 y POSIX.1-2008 o posterior), pero, por supuesto, cualquier información histórica interesante también es interesante.
La pregunta básicamente se reduce a si b siempre tomará el mismo valor que a en el siguiente código:
long int a = /* some valid value */
void *ptr = (void *)a;
long int b = (long int)ptr;
Soy consciente de que esto suele funcionar, pero la pregunta es si es algo correcto (es decir, si los estándares C99 y / o POSIX garantizan que funcionará).
Cuando se trata de C99 parece que no, tenemos 6.3.2.3:
5 Un entero se puede convertir a cualquier tipo de puntero. Excepto como se especificó anteriormente, el resultado está de fi nido por la implementación, podría no estar correctamente alineado, podría no apuntar a una entidad del tipo referenciado y podría ser una representación de captura.56)
6 Cualquier tipo de puntero se puede convertir en un tipo entero. Excepto como se especificó previamente, el resultado está definido por la implementación. Si el resultado no se puede representar en el tipo entero, el comportamiento no se define. El resultado no necesita estar en el rango de valores de cualquier tipo de entero.
Incluso usando intptr_t, el estándar parece garantizar solo que cualquier vacío válido * se puede convertir a intptr_t y viceversa, pero no garantiza que cualquier intptr_t se pueda convertir a vacío * y viceversa.
Sin embargo, todavía es posible que el estándar POSIX lo permita.
No tengo muchas ganas de usar un void * como espacio de almacenamiento para cualquier variable (me parece bastante feo, incluso si POSIX lo permite), pero siento que debo preguntar debido al uso común de la función pthreads_create donde el argumento para start_routine es un entero, y se pasa como void * y se convierte en int o long int en la función start_routine. Por ejemplo, esta página de manual tiene un ejemplo de este tipo (ver enlace para el código completo):
//Last argument casts int to void *
pthread_create(&tid[i], NULL, sleeping, (void *)SLEEP_TIME);
/* ... */
void * sleeping(void *arg){
//Casting void * back to int
int sleep_time = (int)arg;
/* ... */
}
También he visto un ejemplo similar en un libro de texto (Introducción a la programación paralela de Peter S. Pacheco). Teniendo en cuenta que parece ser un ejemplo común usado por personas que deberían saber esto mucho mejor que yo, me pregunto si estoy equivocado y esto es en realidad una cosa segura y portátil.
Como dice, C99 no garantiza que cualquier tipo de entero se pueda convertir a void*
y viceversa sin pérdida de información. Hace una garantía similar para intptr_t
y uintptr_t
definidas en <stdint.h>
, pero esos tipos son opcionales. (La garantía es que un void*
se puede convertir a {u,}intptr_t
y viceversa sin pérdida de información; no existe tal garantía para valores enteros arbitrarios).
POSIX tampoco parece tener ninguna garantía de este tipo.
La descripción POSIX de <limits.h>
requiere que int
y unsigned int
sean al menos de 32 bits. Esto excede el requisito de C99 de que tengan al menos 16 bits. (En realidad, los requisitos son en términos de rangos, no de tamaños, pero el efecto es que int
y unsigned int
debe ser de al menos 32 (bajo POSIX) o 16 (bajo C99) bits, ya que C99 requiere una representación binaria).
La descripción POSIX de <stdint.h>
dice que intptr_t
y uintptr_t
deben tener al menos 16 bits, el mismo requisito impuesto por el estándar C. Como void*
se puede convertir a intptr_t
y viceversa sin pérdida de información, esto implica que void*
puede ser tan pequeño como 16 bits. Combine eso con el requisito POSIX de que int
es de al menos 32 bits (y el requisito POSIX y C de que long
es de al menos 32 bits), y es posible que un void*
no sea lo suficientemente grande como para contener un valor int
o long
sin pérdida de información.
La descripción POSIX de pthread_create()
no contradice esto. Simplemente dice que arg
(el void*
4o argumento de pthread_create()
) se pasa a start_routine()
. Presumiblemente, la intención es que arg
apunte a algunos datos que puede utilizar start_routine()
. POSIX no tiene ejemplos que muestren el uso de arg
.
Puedes ver el estándar POSIX here ; Tienes que crear una cuenta gratuita para acceder a ella.
Creo que su respuesta está en el texto que citó:
Si el resultado no se puede representar en el tipo entero, el comportamiento no se define. El resultado no necesita estar en el rango de valores de cualquier tipo de entero.
Por lo tanto, no necesariamente. Digamos que tenía un long
64 bits y lo lanzó a un void*
en una máquina de 32 bits. Es probable que el puntero sea de 32 bits, por lo que pierde los 32 bits superiores o recupera INT_MAX
. O, potencialmente, algo completamente distinto (indefinido, como lo dice la norma).
El enfoque en las respuestas hasta ahora parece estar en el ancho de un puntero, y de hecho, como lo señala @Nico (y @Quantumboredom también lo señala en un comentario), existe la posibilidad de que intptr_t
sea más ancho que un puntero. La respuesta de @Kevin alude al otro tema importante, pero no lo describe completamente.
Además, aunque no estoy seguro del párrafo exacto en el estándar, Harbison y Steele señalan que intptr_t
y uintptr_t
son tipos opcionales y es posible que ni siquiera existan en una implementación válida de C99. OpenGroup dice que los sistemas compatibles con XSI deben admitir ambos tipos, pero eso significa que POSIX simple, por lo tanto, no los requiere (al menos a partir de la edición de 2003).
Sin embargo, la parte que realmente se ha perdido aquí es que los punteros no siempre tienen que tener una representación numérica simple que coincida con la representación interna de un entero. Esto siempre ha sido así (desde K&R 1978), y estoy bastante seguro de que POSIX tiene cuidado de no anular esta posibilidad tampoco.
Por lo tanto, C99 requiere que sea posible convertir un puntero en un IFF intptr_t
que exista ese tipo, y luego volver a un puntero nuevamente, de modo que el nuevo puntero aún apunte al mismo objeto en la memoria que el puntero antiguo, y de hecho si los punteros tienen una representación no entera. Esto implica que existe un algoritmo que puede convertir un conjunto específico de valores enteros en punteros válidos. Sin embargo, esto también significa que no todos los enteros entre INTPTR_MIN
e INTPTR_MAX
son necesariamente valores de puntero válidos, incluso si el ancho de intptr_t
(y / o uintptr_t
) es exactamente igual al ancho de un puntero .
Por lo tanto, los estándares no pueden garantizar que cualquier intptr_t
o uintptr_t
pueda convertirse en un puntero y volver al mismo valor entero, o incluso qué conjunto de valores enteros puede sobrevivir a dicha conversión, ya que posiblemente no puedan definir todas las reglas y algoritmos posibles para convertir valores enteros en valores de puntero. Hacerlo incluso para todas las arquitecturas conocidas podría impedir la aplicabilidad del estándar a nuevos tipos de arquitecturas aún por inventar.
No estoy seguro de lo que quieres decir con "siempre". No está escrito en ninguna parte del estándar que esté bien, pero no hay sistemas en los que falle.
Si sus enteros son realmente pequeños (por ejemplo, limitados a 16 bits), puede hacerlo estrictamente conforme declarando:
static const char dummy_base[65535];
y luego pasar dummy_base+i
como argumento y recuperarlo como i=(char *)start_arg-dummy_base;