2010-12-06 11 views
6

Tengo este mensaje:pregunta C: eliminar la referencia única en un puntero de indirección doble ** vacío

expected 'void **' but argument is of type 'char **' 

cuando traté de compilar algo similar a esto:

void myfree(void **v) 
{ 
    if(!v || !*v) 
     return; 

    free(*v); 
    *v = NULL; 

    return; 
} 



encontré lo que creo que es una solución después de leer esta pregunta en el desbordamiento de la pila:
Avoid incompatible pointer warning when dealing with double-indirection - Stack Overflow

Así ajusté a algo como esto:

#include <stdio.h> 
#include <stdlib.h> 

void myfree(void *x) 
{ 
    void **v = x; 

    if(!v || !*v) 
     return; 

    free(*v); 
    *v = NULL; 

    return; 
} 

int main(int argc, char *argv[]) 
{ 
    char *test; 

    if((test = malloc(1))) 
    { 
     printf("before: %p\n", test); 
     myfree(&test); 
     printf("after: %p\n", test); 
    } 

    return 0; 
} 

¿Es esta C legal? Estoy desreferenciando un puntero de vacío, ¿no?

Gracias chicos


EDITAR 12/10/2010 16:45 EST:
Como se ha señalado free(NULL) es seguro y regulado por la Norma C. Además, como se explica a continuación, mi ejemplo anterior no es legal C. Consulte la respuesta de caf, la respuesta de Zack y mi propia respuesta.

Por lo tanto, va a ser más fácil para mí para inicializar cualquier punteros a-ser-malloc'd como nulo y más tarde a poco libre() y nulos a cabo directamente en el código:

free(pointer); 
pointer = NULL; 

El razón por la que estaba buscando NULL en myfree() como lo hice fue por mis experiencias con fclose(). fclose(NULL) puede segfault dependiendo de la plataforma (por ejemplo, xpsp3 msvcrt.dll 7.0.2600.5512) y entonces asumí (erróneamente) que lo mismo podría pasar con free(). Había pensado en lugar de complicar mi código con sentencias if que podría implementar mejor en una función.

Gracias a todos por la buena discusión

+3

Algunos me llame a un pagano, pero me acaba de usar 'definir MyFree (x) {hacer libre (x); x = NULL} mientras (0) ' –

+1

No está desreferenciando un 'puntero a vacío'. Está desreferenciando un 'puntero a puntero para anular', lo cual es perfectamente legal. – aschepler

+1

En realidad, quiero corregir mi macro anterior: '#define myfree (x) do {void ** tmp_ = & x; libre (* tmp_); * tmp_ = NULL; } while (0) '(¡Pensé que no había forma de evitar la doble evaluación de' x' pero sí!) –

Respuesta

4

No, esto es no C legal, a menos que pase la dirección de un objeto void *-myfree() (por lo que también podría simplemente mantener su definición original).

La razón es que en su ejemplo, un objeto de tipo char * (el objeto declarado como test en main()) se modifica a través de un lvalue de tipo void * (la lvalue *v en myfree()). §6.5 de los C estados estándar:

7 Un objeto tendrá su valor almacenado accedidos solamente por un expresión lvalue que tiene uno de los siguientes tipos:

— a type compatible with the effective type of the object, 
— a qualified version of a type compatible with the effective type of 
the object, 
— a type that is the signed or unsigned type corresponding to the effective 
type of the object, 
— a type that is the signed or unsigned type corresponding to a qualified 
version of the effective type of the object, 
— an aggregate or union type that includes one of the aforementioned 
types among its members (including, recursively, a member of a subaggregate 
or contained union), or 
— a character type. 

Desde void * y char * no son tipos compatibles, esta restricción se ha roto. La condición para dos tipos de puntero para que sea compatible se describe en §6.7.5.1:

Durante dos tipos de puntero sean compatibles , ambos serán idénticamente cualificado y ambos serán punteros a tipos compatibles.

Para lograr el efecto deseado, debe utilizar una macro:

#define MYFREE(p) (free(p), (p) = NULL) 

(No hay necesidad de comprobar si hay NULL, ya free(NULL) es legal Tenga en cuenta que esta macro evalúa p dos veces.).

+0

Se requiere que * cualquier * puntero a objeto sea convertible a 'void *' y viceversa sin pérdida de información; por lo tanto, borrar un puntero a objeto a través de un alias 'void *' es abrumadoramente propenso a DTRT incluso cuando no todos los punteros tienen la misma representación. El único caso en el que podría no serlo es si la * constante de puntero nulo * no es todos los bits cero para todos los tipos de puntero a objeto, pero nadie de AFAIK, realmente nadie lo hace nunca más. Supongo que también podría tener que preocuparse por la optimización excesiva debido a las reglas de aliasing basadas en tipo, feh. – zwol

+0

¿Citar el estándar? Esto no suena del todo bien ... –

+0

Además, se me ocurre que la semántica de 'malloc' y' free' en sí misma requiere que la constante del puntero nulo sea el mismo patrón de bit para todos los punteros a objeto. – zwol

2

Esto es perfectamente legal, pero puede resultar confuso para otras personas que leen el código.

También es posible usar la fundición para eliminar la advertencia:

myfree((void **)&rest); 

Esto es más legible y comprensible.

+0

void ** no es un puntero genérico al puntero. – liuyang1

1

En C no tiene más remedio que presentar un reparto en algún lugar de aquí. Me gustaría utilizar una macro para asegurar que las cosas se hacen correctamente en el lugar de llamada:

void 
myfree_(void **ptr) 
{ 
    if (!ptr || !*ptr) return; 
    free(*ptr); 
    *ptr = 0; 
} 
#define myfree(ptr) myfree_((void **)&(ptr)) 

[en realidad se podría nombrar tanto la función como la "MyFree" macro, gracias a C de no-infinito-macro-recursividad reglas ! Pero sería confuso para los lectores humanos. Según la larga discusión debajo de la respuesta de café, también estipularé que la declaración *ptr = 0 aquí modifica un objeto de tipo desconocido a través de un alias void**, que es un comportamiento indefinido en el tiempo de ejecución; sin embargo, mi opinión es que no causará problemas en la práctica , y es la opción menos mala disponible en C simple; La macro de café que evalúa su argumento dos veces parece mucho más probable (para mí) causar problemas reales.]

En C++ puede usar una función de plantilla, que es mejor en tres aspectos: evita tener que tomar la dirección de nada en el sitio de la llamada, no se rompe la corrección del tipo, y obtendrá un error de tiempo de compilación en lugar de un bloqueo en tiempo de ejecución si accidentalmente pasa un puntero a myfree.

template <typename T> 
void 
myfree(T*& ptr) 
{ 
    free((void *)ptr); 
    ptr = 0; 
} 

Pero, por supuesto, en C++ tiene incluso mejores opciones disponibles, como el puntero inteligente y clases de contenedor.

Debería mencionarse, finalmente, que los programadores expertos de C evitan este tipo de envoltorio, porque no le ayuda cuando hay otra copia del puntero a la memoria que acaba de liberar colgando en algún lado, y eso es exactamente cuando necesitas ayuda.

+0

Normalmente escribo la función y la macro con los mismos nombres, y cuando llamo a la función (en la macro o en otro lugar) la escribo como '(myfree) (x);', que a) desactiva la expansión macro del preprocesador, b) aclara que no estoy llamando a la macro, yc) le permite pasar 'myfree' (en lugar de' myfree_') como un puntero a la función. –

+0

Eso es un buen truco, yah. – zwol

+0

Oooh, me gusta la idea de esconder el elenco dentro de una macro. Me gusta su camino aquí mucho mejor que ocultar la configuración a NULL dentro de una macro, porque su camino aquí preserva el operador de dirección (&), que es una señal visual importante que el puntero cambiará (por ejemplo, se pondrá a cero). –

1

caf's la respuesta es correcta: No, no es legal. Y como Zack señala que violar la ley de esta manera aparentemente es menos probable que cause problemas.

Encontré lo que parece ser otra solución en el comp.lang.c FAQ list · Question 4.9, que señala que se debe usar un valor de vacío intermedio.

#include <stdio.h> 
#include <stdlib.h> 

void myfree(void **v) 
{ 
    if(!v) 
     return; 

    free(*v); 
    *v = NULL; 

    return; 
} 

int main(int argc, char *argv[]) 
{ 
    double *num; 

    if((num = malloc(sizeof(double)))) 
    { 
     printf("before: %p\n", num); 

     { 
      void *temp = num; 
      myfree(&temp); 
      num = temp; 
     } 

     printf("after: %p\n", num); 
    } 

    return 0; 
}