2011-12-23 29 views
17

¿Qué significa convertir un valor entero a void* o viceversa desde el punto de vista de la memoria? Mi comprensión es void* es una dirección a un bloque de memoria de longitud no especificada.
Esto parece ser algo así como comparar la manzana con las naranjas.¿Qué significa convertir int a void * o viceversa?

int myval = 5; 
void* ptr = (void*)myval; 
printf("%d",(int)ptr); 

Me di cuenta de que debería haber dado el contexto exacto en el que se utiliza.

int main(int argc, char* argv[]) { 
long  thread; /* Use long in case of a 64-bit system */ 
pthread_t* thread_handles; 

/* Get number of threads from command line */ 
if (argc != 2) Usage(argv[0]); 
thread_count = strtol(argv[1], NULL, 10); 
if (thread_count <= 0 || thread_count > MAX_THREADS) Usage(argv[0]); 

thread_handles = malloc (thread_count*sizeof(pthread_t)); 

for (thread = 0; thread < thread_count; thread++) 
    pthread_create(&thread_handles[thread], NULL, Hello, (void*) thread); 

printf("Hello from the main thread\n"); 

for (thread = 0; thread < thread_count; thread++) 
    pthread_join(thread_handles[thread], NULL); 

free(thread_handles); 
return 0; 
} /* main */ 

/*-------------------------------------------------------------------*/ 
void *Hello(void* rank) { 
long my_rank = (long) rank; /* Use long in case of 64-bit system */ 

printf("Hello from thread %ld of %d\n", my_rank, thread_count); 

return NULL; 
} /* Hello */ 

Este código es del libro de Peter Pachecho sobre programación paralela.

+0

Posible duplicado de: http://stackoverflow.com/questions/3568069/is-it-safe-to-cast-an-int-to-void-pointer-and-back-to-int-again –

+1

Es aún peor ... al menos como comparar manzanas con patatas – alk

Respuesta

17

Casting int a void * es bastante insignificante y no debe hacerse ya que estaría intentando lanzar un puntero a un puntero. Citando el C99 standard, sección 6.3.2.3 elemento 5:

Un entero se puede convertir a cualquier tipo de puntero. Excepto como se especificó anteriormente, el resultado está definido por la implementación, podría no estar alineado correctamente con , podría no apuntar a una entidad del tipo al que se hace referencia, y podría ser una representación de captura.

Usted podía fundido a int *void * (cualquier puntero se puede convertir en void * que se puede pensar en como el "tipo de base" de todos los punteros).

casting void * a int no es portátil y puede ser completamente erróneo dependiendo de la plataforma que utiliza (por ejemplo, tal vez void * 64 bits de ancho y int puede ser sólo de 32 bits). Citando nuevamente el estándar C99, sección 6.3.2.3 elemento 6:

Cualquier tipo de puntero se puede convertir a un tipo entero. Excepto como previamente especificado, el resultado está definido por la implementación. Si el resultado no se puede representar en el tipo entero, el comportamiento es undefined. El resultado no necesita estar en el rango de valores de cualquier tipo entero .

Para resolver esto, algunas plataformas proporcionan uintptr_t que le permite tratar un puntero como un valor numérico del ancho adecuado.

3

Tanto el puntero void* (o cualquier puntero) y int son, aproximadamente, números. Pueden tener diferentes tamaños de bits, pero es poco probable que el puntero sea más pequeño que int, por lo que la operación es reversible. Por supuesto, es ilegal y nunca debe desreferenciar el puntero que no tiene una ubicación válida para señalar.

2

Esto es como comparar manzanas y naranjas. La única forma de que este código funcione es porque lo estás lanzando explícitamente hacia adelante y hacia atrás.

El compilador de C solo copia los bytes del int en el espacio para el puntero y la parte posterior, al igual que cómo puede mantener un char en un int.

Esto podría causar que su número se estropee si por alguna razón un 'int' es más largo que un 'void *' en su plataforma.

Si realmente desea tener un número para convertir enteros en punteros y viceversa, consulte intptr_t. Ese tipo está diseñado para almacenar números enteros y punteros.

1

Es útil poder convertir un puntero a tipo entero para la aritmética del puntero. Por ejemplo, la macro offsetof(), que calcula el desplazamiento de un miembro dentro de una estructura, necesita este tipo de conversión.

Sin embargo, se debe asegurar que el tipo primitivo utilizado para esto sea capaz de manejar un puntero: aquí, para 64 Linux por ejemplo, cuando se usa gcc, este no es el caso: void * tiene tamaño 8, un int tiene 4. tamaño

la macro offsetof se define como tal:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 

se utiliza un lugar destacado en la aplicación lista doblemente enlazada del núcleo de Linux, muy simplemente definida como:

struct list_head { struct list_head *prev, *next; } 

Esta estructura está incrustado dentro de las estructuras del núcleo, como en:

struct something { 
    //... 
    struct list_head list; 
    //... 
} 

Al caminar una lista, el código tiene que tomar un puntero a la estructura de codificación.Esto se hace como este (definición simplificada):

#define container_of(ptr, type, member) \ 
    (type *)((char *)ptr - offsetoff(type, member)) 
#define list_entry(list, type, member) \ 
    container_of(list, type, member) 

Y en el código, muy a menudo, véase:

struct something *s; 
list_for_each(listptr, somehead) { 
    s = list_entry(listptr, struct something, list); 
    //... 
} 

que simplemente no sería factible sin este tipo de macro y el puntero de la aritmética. Pero es muy dependiente de la cadena de herramientas.

+0

¿Qué tiene esto que ver con 'offsetof'. Además, 'offsetof' es una plataforma definida, una implementación del compilador es libre de implementarlo de la manera que mejor se adapte. E.g gcc implementa esto por un intrínseco. –

1

Sería como comparar manzanas con naranjas si se intentara hacer alguna comparación. Pero no hay. Básicamente, en la mayoría de las arquitecturas que existen, un int se puede convertir en un puntero de vacío sin la pérdida de información, y un puntero de vacío también se puede devolver a un int, nuevamente sin pérdida de información. Por supuesto, no debe intentar desreferenciar ese puntero, porque no apunta a ninguna ubicación de memoria significativa: es simplemente una variable que contiene el mismo patrón de bits que el patrón de bits que el entero solía contener antes del reparto.

+0

No es cierto que no haya pérdida de información. El estándar C99 dice: "Cualquier tipo de puntero se puede convertir a 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 está definido. El resultado no necesita ser en el rango de valores de cualquier tipo entero ". – bobbymcr

+0

Lo siento, edité mi respuesta y corrigí ese error. (Edité el bit sobre C99, y la palabra "vasto" en el término "gran mayoría"). –

+0

"La inmensa mayoría" probablemente sea incluso falsa hoy, y será cada vez menos probable en el futuro. Las arquitecturas modernas son en su mayoría con 64 punteros y 32 bits 'int'. –

3

El estándar C especifica que debe ser posible convertir un puntero vacío en un tipo integral, de modo que al convertir el tipo integral en un puntero vacío se obtendrá el mismo puntero. No estoy seguro si se requiere que la conversión de un puntero nulo a un entero arroje el valor cero, o si solo se reconocen los ceros numéricos literales como especiales. En muchas implementaciones, el valor entero representa una dirección de hardware, pero el estándar no ofrece dicha garantía. Sería completamente posible que en un hardware que incluyera una trampa especial para la dirección de hardware 0x12345678, convertir un puntero a un entero restara 0x12345678 de la dirección de hardware, y convertir un entero a un puntero agregaría 0x12345678 de nuevo (por lo tanto, un valor entero de cero representaría un puntero nulo).

En muchos casos, especialmente al desarrollar controladores embebidos, el proveedor del compilador especificará explícitamente a qué dirección de hardware se accederá al convertir un valor entero particular a un tipo de puntero. En procesadores con un espacio de dirección lineal único, la conversión de un valor entero de 0x12345678 a un puntero generalmente arrojaría un puntero que hace referencia a la dirección 0x12345678; el manual de referencia de hardware indicaría si había algo especial sobre esa ubicación. En los procesadores con espacios de direcciones más "interesantes", puede ser necesario usar algo que no sea la dirección de hardware como puntero. Por ejemplo, en computadoras PC antiguas de IBM, el búfer de pantalla se asignó a la dirección de hardware 0xB8000, pero en la mayoría de los compiladores, la dirección se expresaría como (muy lejos *) 0xB8000000.