2008-10-29 6 views
6

he visto una función cuyo prototipo es:¿Es nulo ** un tipo aceptable en ANSI-C?

int myfunc(void** ppt) 

Esta función se llama en un archivo de C como un = myfunc (MyStruct ** Q1);

donde mystruct es typedef para uno de la estructura que tenemos.

Esto funciona sin errores de compilación en MSVC6.0, pero cuando compilo con algún otro compilador de C, que da un error en el lugar donde esta función es llamada con el mensaje de error:

argumento de tipo MyStruct ** es incompatible con el parámetro de tipo void **

El argumento de myfunc() se mantiene como nula **, ya que parece ser una especie malloc genérica de función a ser llamada con varios tipos de variables de estructura para la memoria asignación

  1. ¿Hay algún tipo como vacío ** permitido en C estándar/cualquier compilador de C?
  2. ¿Cómo puedo solucionar esto? [He intentado colar el argumento de llamada de función para mystruct**, pero no funcionó]

-AD

Respuesta

9

void** es válido, pero, sobre la base de su mensaje de error, es probable que tenga que convierta explícitamente el argumento de la siguiente manera:

mystruct **var1; 
x = myfunc ((void**) var1); 

Eso es porque la función myfunc está a la espera del tipo void**. Mientras que void* se puede convertir implícitamente a cualquier otro puntero, no es así para el puntero doble; debe explicitarlo.

+3

Esto invoca un comportamiento indefinido (modificación de un objeto a través de un puntero a un tipo incorrecto). La API simplemente está rota; cualquier función de tipo asignación que asigne memoria genérica ('void *') necesita devolver el puntero en lugar de tomar un argumento de doble puntero. Afortunadamente, estas API rotas solo se pueden envolver para arreglarlas ... –

+1

Modificarlo mediante un puntero diferente puede no estar bien definido, pero eso no se está haciendo aquí. Estoy lanzando para deshacerme del error y todos los punteros se pueden convertir hacia y desde punteros vacíos de una manera definida. Para evitar UB, tendrá que devolverlo al tipo correcto antes de intentar desreferenciarlo; esto es un hecho ya que no puede eliminar la referencia de un tipo 'void *' de todos modos: el compilador no conoce el tipo subyacente. El OP dejó en claro que el uso de un puntero de vacío era para poder enviar diferentes tipos de puntero, por lo que parece aceptable aquí y solo hay UB si no se devuelve correctamente. – paxdiablo

+1

La función llamada luego modifica (o al menos accede) el objeto a través del tipo incorrecto. Lee o escribe como 'void *' cuando el tipo real es 'mystruct *' y estos tipos no son compatibles. 'void **' no es como 'void *'; no es un doble puntero universal. –

1

Esta pregunta es un poco confusa. Pero sí, void ** es ciertamente legal y válido C, y significa "puntero a puntero a void" como se esperaba.

No estoy seguro acerca de su ejemplo de una llamada, los argumentos ("mystruct ** var1") no tienen sentido. Si var1 es de tipo mystruct **, la llamada debería simplemente leer a = func(var1);, esto podría ser un error tipográfico.

La conversión debería funcionar, pero debe enviar a void **, ya que eso es lo que espera la función.

0

Tan sucio como puede parecer: a veces no puedes resolver un problema sin usar void **.

3

Hay una razón por la que el compilador no puede convertir automáticamente desde mystruct** a void**.

Considere el siguiente código:

void stupid(struct mystruct **a, struct myotherstruct **b) 
{ 
    void **x = (void **)a; 
    *x = *b; 
} 

El compilador no se quejará de la conversión implícita de myotherstruct* a void* en la línea *x = *b, a pesar de que esa línea está tratando de poner un puntero a una myotherstruct en un lugar donde solo deben colocarse punteros a mystruct.

El error es, de hecho, en la línea anterior, que es la conversión "un puntero a un lugar donde los punteros a mystruct se pueden poner" a "un puntero a un lugar donde los punteros a nada se pueden poner". Este es la razón por la cual no hay un molde implícito. Por supuesto, cuando usas un molde explícito, el compilador supone que sabes lo que estás haciendo.

0

Sí, void ** es perfectamente aceptable y bastante útil en determinadas circunstancias. Tenga en cuenta también que, dada la declaración void **foo y void *bar, el compilador conocerá el tamaño del objeto señalado por foo (apunta a un puntero y todos los punteros son del mismo tamaño excepto en algunas plataformas antiguas de las que no debe preocuparse) , pero no sabrá el tamaño del objeto al que apunta la barra (podría apuntar a un objeto de cualquier tamaño). Por lo tanto, puede realizar con seguridad la aritmética del puntero en los punteros void **, pero no en los punteros void *. Primero debe convertirlos a otra cosa, como char *. GCC le permitirá simular que void * y char * son equivalentes, cada uno apuntando a un objeto de un solo byte de tamaño, pero esto no es estándar y no es portable.

20

El comp.lang.c FAQ aborda este problema en detalle en Question 4.9. En resumen, dicen no es estrictamente portátil para lanzar un puntero a puntero arbitrario a un void **; continúan explicando que "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)". Continúan explicando que "cualquier valor void ** con el que juegue debe ser la dirección de un valor real void * en algún lado; los títulos como (void **)&dp, aunque pueden cerrar el compilador, no son portables (y es posible que ni siquiera hagan lo que usted desea)".

Por lo tanto, se puede con seguridad/portable conseguir el comportamiento deseado con un código como:

some_type *var1 = foo(); 
void *tmp_void_ptr = (void *)var1; 
myfunc(&tmp_void_ptr); 
Cuestiones relacionadas