Aunque es posible escribir código genérico en C usando un puntero void (puntero genérico), encuentro que es bastante difícil depurar el código ya que el puntero void puede tomar cualquier tipo de puntero sin advertencia del compilador (por ejemplo, la función foo() toma el puntero void que se supone puntero a struct, pero el compilador no se queja si se pasa la matriz char.) ¿Qué tipo de enfoque/estrategia utilizas cuando utilizas el puntero void en C?programación genérica en C con puntero de vacío
Respuesta
La solución es no utilizar void*
a menos que realmente, realmente tenga que hacerlo. Los lugares donde realmente se requiere un puntero void son muy pequeños: parámetros para las funciones de subproceso y un puñado de otros lugares donde debe pasar datos específicos de la implementación a través de una función genérica. En todos los casos, el código que acepta el parámetro void*
solo debe aceptar un tipo de datos pasado a través del puntero void, y el tipo debe documentarse en los comentarios y ser obedecido servilmente por todos los llamantes.
El OP mencionó específicamente la programación genérica, que es un caso de uso bastante bueno para void * (en ausencia de instalaciones de lenguaje de nivel superior como plantillas o macros agradables). –
Es decir, por ejemplo , qsort() y bsearch() –
@Matt, qsort y bsearch son ejemplos de "datos específicos de implementación a través de una función genérica". –
Esto podría ayudar:
comp.lang.c FAQ list · Question 4.9
Q: Supongamos que quiero escribir una función que toma un puntero genérico como un argumento y quiero simular pasándolo por referencia. ¿Puedo dar el tipo de parámetro formal void **, y hacer algo como esto?
void f(void **);
double *dp;
f((void **)&dp);
A: Not portably. Código como este puede funcionar y, a veces, se recomienda, pero se basa en todos los tipos de punteros que tienen la misma representación interna (que es común, pero no universal, ver pregunta 5.17).
No hay ningún tipo genérico de puntero a puntero en C. void * actúa como un puntero genérico solo porque las conversiones (si es necesario) se aplican automáticamente cuando se asignan otros tipos de puntero ay desde void * 's; estas conversiones no se pueden realizar si se intenta indirectamente sobre un valor nulo ** que apunta a un tipo de puntero distinto de void *. Cuando utiliza un valor de puntero void ** (por ejemplo, cuando utiliza el operador * para acceder al valor nulo * al que apunta el vacío **), el compilador no tiene forma de saber si ese valor nulo * fue alguna vez convertido desde algún otro tipo de puntero. Debe suponer que no es más que un vacío *; no puede realizar conversiones implícitas.
En otras palabras, cualquier valor de nulo ** con el que juegue debe ser la dirección de un valor nulo * real en alguna parte; los lanzamientos como (nulo **) & dp, aunque pueden cerrar el compilador, no son portables (y es posible que ni siquiera hagan lo que quieran, consulte también la pregunta 13.9). Si el puntero al que apunta el vacío ** no es un vacío *, y si tiene un tamaño o representación diferente a un vacío *, entonces el compilador no podrá acceder a él correctamente.
Para hacer que el fragmento de código anterior trabajo, que tendría que utilizar un vacío intermedio variables *:
double *dp;
void *vp = dp;
f(&vp);
dp = vp;
Las asignaciones hacia y desde vp dar el compilador la oportunidad de realizar ninguna conversión, si es necesario.
Una vez más, la discusión asume que diferentes tipos de punteros pueden tener diferentes tamaños o representaciones, lo cual es raro hoy en día, pero no es algo inaudito. Para apreciar el problema con el vacío ** más claramente, compare la situación con una análoga que involucre, digamos, tipos int y doble, que probablemente tengan diferentes tamaños y ciertamente tengan diferentes representaciones. Si tenemos una función
void incme(double *p)
{
*p += 1;
}
entonces podemos hacer algo como
int i = 1;
double d = i;
incme(&d);
i = d;
y voy a estar incrementado en 1. (Esto es análogo al vacío correcta ** código que implica la vp auxiliar.) Si, por el contrario, estábamos a intentar algo así como
int i = 1;
incme((double *)&i); /* WRONG */
(este código es análogo al fragmento en la pregunta), sería muy poco probable que funcione.
Todos sabemos que el sistema de tipos C es básicamente basura, pero trate de no hacer eso ... Todavía tiene algunas opciones para tratar con tipos genéricos: uniones y punteros opacos.
De todos modos, si una función genérica está tomando un puntero de vacío como parámetro, ¡no debería intentar desreferenciarlo !.
El enfoque/estrategia es minimizar el uso de punteros void *. Se necesitan en casos específicos. Si realmente necesita pasar el vacío *, también debe pasar el tamaño del objetivo del puntero.
Esta función de intercambio genérico le ayudará mucho en la comprensión de vacío genérica *
#include<stdio.h>
void swap(void *vp1,void *vp2,int size)
{
char buf[100];
memcpy(buf,vp1,size);
memcpy(vp1,vp2,size);
memcpy(vp2,buf,size); //memcpy ->inbuilt function in std-c
}
int main()
{
int a=2,b=3;
float d=5,e=7;
swap(&a,&b,sizeof(int));
swap(&d,&e,sizeof(float));
printf("%d %d %.0f %.0f\n",a,b,d,e);
return 0;
}
¿Qué ocurre si el tamaño es> 100? –
solución de Arya se puede cambiar un poco para apoyar un tamaño variable:
#include <stdio.h>
#include <string.h>
void swap(void *vp1,void *vp2,int size)
{
char buf[size];
memcpy(buf,vp1,size);
memcpy(vp1,vp2,size);
memcpy(vp2,buf,size); //memcpy ->inbuilt function in std-c
}
int main()
{
int array1[] = {1, 2, 3};
int array2[] = {10, 20, 30};
swap(array1, array2, 3 * sizeof(int));
int i;
printf("array1: ");
for (i = 0; i < 3; i++)
printf(" %d", array1[i]);
printf("\n");
printf("array2: ");
for (i = 0; i < 3; i++)
printf(" %d", array2[i]);
printf("\n");
return 0;
}
- 1. Puntero a vacío en C++?
- 2. Cómo declarar un puntero vacío en C#
- 3. Programación genérica v.s. Metaprogramación
- 4. Ir equivalente a un puntero de vacío en C
- 5. ¿Cuál es el significado de "programación genérica" en C++?
- 6. ¿Cómo se hace programación genérica en Haskell?
- 7. C: Tipo de extrapolación desde el puntero de vacío
- 8. pasando el vacío a una clase genérica
- 9. Cuándo usar un puntero de vacío?
- 10. genérica puntero de función miembro como un parámetro de plantilla
- 11. programación C: Desreferenciar puntero al error de tipo incompleto
- 12. implementación genérica en C
- 13. iterador "genérica" en C++
- 14. herencia genérica en C#?
- 15. ¿Cambios de bits en un puntero C?
- 16. Comportamiento de puntero inesperado en C++
- 17. pregunta C: eliminar la referencia única en un puntero de indirección doble ** vacío
- 18. punteros vacío en C++
- 19. Programación C: malloc() para una matriz 2D (usando puntero a puntero)
- 20. ¿Cuál es la diferencia entre un puntero de vacío y un puntero NULL?
- 21. ¿Cuáles son las razones para emitir un puntero vacío?
- 22. Puntero a un puntero en objetivo-c?
- 23. Búsqueda binaria genérica en C#
- 24. puntero Inicializar a puntero con múltiples operadores de dirección en C o C++
- 25. (nil) puntero en C/C++
- 26. Eliminar un directorio no vacío mediante programación en C o C++
- 27. ¿Aumentar el puntero del vacío en un byte? ¿por dos?
- 28. Diseño moderno en C++ Programación genérica y patrones de diseño aplicados
- 29. puntero en C
- 30. C/C++ Puntero Pregunta
Si esto es una pregunta C solo quita la etiqueta C++ –
Hmmm ... "Cuando golpeo mi dedo con un martillo duele a lo grande. ¿Qué enfoque utilizas cuando golpeas partes de tu cuerpo con un martillo?" – sharptooth
Si crees que la programación C generic con void * puede ser elegante/agradable de usar, mira lo que tienes que hacer para usar la función qsort ... –