¿Qué significa void * y cómo usarlo?

147

Hoy, cuando estaba leyendo el código de otros, vi algo como void *func(void* i);, ¿qué significa esto void*aquí para el nombre de la función y para el tipo de variable, respectivamente?

Además, ¿cuándo necesitamos usar este tipo de puntero y cómo usarlo?

OneZero
fuente
2
¿Qué libro de C estás usando? Estás pidiendo la mejor parte de un capítulo completo.
cnicutar
1
Eche un vistazo a stackoverflow.com/questions/692564/…
WDan
Sigue el ejemplo de mallocy calloc. La página de manual continúa diciendo: "... devuelve un puntero a la memoria asignada, que está alineada adecuadamente para cualquier tipo de datos incorporado".
autómata

Respuestas:

175

Un puntero a voides un tipo de puntero "genérico". A void *se puede convertir a cualquier otro tipo de puntero sin una conversión explícita. No puede desreferenciar void *ni hacer aritmética de puntero con él; primero debe convertirlo en un puntero a un tipo de datos completo.

void *a menudo se usa en lugares donde necesita poder trabajar con diferentes tipos de puntero en el mismo código. Un ejemplo comúnmente citado es la función de biblioteca qsort:

void qsort(void *base, size_t nmemb, size_t size, 
           int (*compar)(const void *, const void *));

basees la dirección de una matriz, nmembes el número de elementos en la matriz, sizees el tamaño de cada elemento y compares un puntero a una función que compara dos elementos de la matriz. Se llama así:

int iArr[10];
double dArr[30];
long lArr[50];
...
qsort(iArr, sizeof iArr/sizeof iArr[0], sizeof iArr[0], compareInt);
qsort(dArr, sizeof dArr/sizeof dArr[0], sizeof dArr[0], compareDouble);
qsort(lArr, sizeof lArr/sizeof lArr[0], sizeof lArr[0], compareLong);

Las expresiones de matriz iArr, dArry lArrse convierten implícitamente a partir de los tipos de matriz a los tipos de puntero en la llamada de función, y cada uno se convierten implícitamente de "puntero a int/ double/ long" a "puntero a void".

Las funciones de comparación se verían así:

int compareInt(const void *lhs, const void *rhs)
{
  const int *x = lhs;  // convert void * to int * by assignment
  const int *y = rhs;

  if (*x > *y) return 1;
  if (*x == *y) return 0;
  return -1;
}

Al aceptar void *, qsortpuede trabajar con matrices de cualquier tipo.

La desventaja de usar void *es que arrojas la seguridad de tipografía por la ventana y hacia el tráfico que viene. No hay nada que lo proteja de usar la rutina de comparación incorrecta:

qsort(dArr, sizeof dArr/sizeof dArr[0], sizeof dArr[0], compareInt);

compareIntespera que sus argumentos apunten a ints, pero en realidad está trabajando con doubles. No hay forma de detectar este problema en tiempo de compilación; terminarás con una matriz mal ordenada.

John Bode
fuente
55
En realidad, no se garantiza que void*se pueda convertir a un puntero de función. Pero para los apuntadores de datos, lo que dijiste es válido.
Vatine
Antes de que los punteros nulos estuvieran disponibles, se usó "char *" en su lugar. Pero el vacío es mejor ya que no se puede usar para alterar nada directamente.
user50619
22

Usar un vacío * significa que la función puede tomar un puntero que no necesita ser de un tipo específico. Por ejemplo, en las funciones de socket, tiene

send(void * pData, int nLength)

Esto significa que puede llamarlo de muchas maneras, por ejemplo

char * data = "blah";
send(data, strlen(data));

POINT p;
p.x = 1;
p.y = 2;
send(&p, sizeof(POINT));
TheSteve
fuente
Así que es muy parecido a los genéricos en otros idiomas, pero sin verificación de tipo, ¿verdad?
Winger Sendon el
3
Supongo que sería similar, sin embargo, dado que no hay verificación de tipo, cometer un error puede causar resultados muy extraños o hacer que el programa se bloquee por completo.
TheSteve
7

C es notable a este respecto. Se puede decir voidque la nada void*es todo (puede ser todo).

Es solo este pequeño *que hace la diferencia.

Rene lo ha señalado. A void *es un puntero a alguna ubicación. Lo que hay cómo "interpretar" se deja al usuario.

Es la única forma de tener tipos opacos en C. Se pueden encontrar ejemplos muy destacados, por ejemplo, en bibliotecas de estructura de datos generales o glib. Se trata de manera muy detallada en "Interfaces e implementaciones C".

Le sugiero que lea el capítulo completo e intente comprender el concepto de un puntero para "obtenerlo".

Friedrich
fuente
5
void*

es un "puntero a la memoria sin suposiciones de qué tipo hay almacenado". Puede usar, por ejemplo, si desea pasar un argumento a la función y este argumento puede ser de varios tipos y en función manejará cada tipo.

René Kolařík
fuente
3

Puede echar un vistazo a este artículo sobre punteros http://www.cplusplus.com/doc/tutorial/pointers/ y leer el capítulo: punteros nulos .

Esto también funciona para el lenguaje C.

El tipo de puntero vacío es un tipo especial de puntero. En C ++, void representa la ausencia de tipo, por lo que los punteros vacíos son punteros que apuntan a un valor que no tiene tipo (y, por lo tanto, también una longitud indeterminada y propiedades de desreferencia indeterminadas).

Esto permite que los punteros nulos apunten a cualquier tipo de datos, desde un valor entero o flotante a una cadena de caracteres. Pero a cambio tienen una gran limitación: los datos señalados por ellos no pueden ser directamente desreferenciados (lo cual es lógico, ya que no tenemos ningún tipo para desreferenciar), y por esa razón siempre tendremos que emitir la dirección en el puntero vacío para algún otro tipo de puntero que apunte a un tipo de datos concreto antes de desreferenciarlo.

AG
fuente
3

Un puntero vacío se conoce como puntero genérico. Me gustaría explicar con un ejemplo de escenario pthread.

La función de hilo tendrá el prototipo como

void *(*start_routine)(void*)

Los diseñadores de la API pthread consideraron el argumento y los valores de retorno de la función thread. Si esas cosas se hacen genéricas, podemos escribir cast to void * mientras se envía como argumento. Del mismo modo, el valor de retorno se puede recuperar de void * (pero nunca usé valores de retorno de la función de hilo).

void *PrintHello(void *threadid)
{
   long tid;

   // ***Arg sent in main is retrieved   ***
   tid = (long)threadid;
   printf("Hello World! It's me, thread #%ld!\n", tid);
   pthread_exit(NULL);
}

int main (int argc, char *argv[])
{
   pthread_t threads[NUM_THREADS];
   int rc;
   long t;
   for(t=0; t<NUM_THREADS; t++){
      //*** t will be type cast to void* and send as argument.
      rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);   
      if (rc){
         printf("ERROR; return code from pthread_create() is %d\n", rc);
         exit(-1);
      }
   }    
   /* Last thing that main() should do */
   pthread_exit(NULL);
}
Jeyaram
fuente
¿Por qué llamar en pthread_exit(NULL);lugar de return 0;al final de main?
Seabass77
1
@ Seabass77 Consulte stackoverflow.com/questions/3559463/…
Jeyaram
1

a void*es un puntero, pero el tipo de lo que apunta no está especificado. Cuando pasa un puntero vacío a una función, necesitará saber cuál era su tipo para poder volver a ese tipo correcto más adelante en la función para usarlo. Verá ejemplos en los pthreadsque se utilizan funciones con exactamente el prototipo en su ejemplo que se utilizan como la función de hilo. Luego puede usar el void*argumento como un puntero a un tipo de datos genérico de su elección y luego volverlo a ese tipo para usar dentro de su función de hilo. Sin embargo, debe tener cuidado al usar punteros vacíos, ya que a menos que vuelva a utilizar un puntero de su tipo real, puede terminar con todo tipo de problemas.

matemático1975
fuente
1

El estándar C11 (n1570) §6.2.2.3 al1 p55 dice:

Un puntero a voidpuede convertirse ao desde un puntero a cualquier tipo de objeto. Un puntero a cualquier tipo de objeto puede convertirse en un puntero para anular y volver de nuevo; el resultado se comparará igual al puntero original.

Puede usar este puntero genérico para almacenar un puntero a cualquier tipo de objeto, pero no puede usar operaciones aritméticas habituales con él y no puede deducirlo.

md5
fuente
0

La función toma un puntero a un tipo arbitrario y devuelve uno.

glglgl
fuente