Cuando programamos en C, generalmente creamos estructuras de datos que inicializamos y luego liberamos cuando ya no se necesitan. Por ejemplo, si queremos crear una matriz dinámica de doble, es común declarar
struct vector { double *data; int size; int capacity; } typedef struct vector vector; vector *v_new(int n) { vector *v = malloc(sizeof(vector)); v->data = malloc(n * sizeof(double)); v->size = n; v->capacity = n; return v; } La pregunta es sobre los patrones comunes para una función libre. En C, la función free acepta el puntero NULL y no hace nada. ¿Es un patrón común diseñar funciones v_free de esa manera, o generalmente esperan un puntero que no sea NULL? Para que quede claro, ¿esperaría que esta implementación
void v_free(vector *v) { if (v != NULL) { free(v->data); } free(v); }o este ?
void v_free(vector *v) { free(v->data); free(v); }Esta pregunta se hace porque comenzamos a enseñar C a estudiantes de pregrado en la escuela preparatoria en Francia, y no tenemos tanta experiencia en "Patrones de diseño C".
Gracias por su consejo.
No puede acceder a v->data si v es NULL. Entonces, si existe la posibilidad de eso, debe hacer la versión que verifica eso, que está mejor escrito como
void v_free(vector *v) { if (v != NULL) { free(v->data); free(v); } } Si v nunca debe ser NULL aquí, quizás sea mejor agregar una afirmación para hacer explícita la suposición:
void v_free(vector *v) { assert(v != NULL); free(v->data); free(v); }De esa forma el programador notará que está haciendo algo mal.
Tenga en cuenta que ninguna de las versiones detecta un puntero colgante, es decir. puntero que apunta a un objeto ya destruido. Esto incluye apuntar a la memoria ya liberada (es decir, tendría el doble de libre aquí) o apuntando a una variable local que ya no está dentro del alcance.
La pregunta es sobre los patrones comunes para una función libre. En C, la función free acepta el puntero NULL y no hace nada. ¿Es un patrón común diseñar funciones v_free de esa manera, o generalmente esperan un puntero que no sea NULL?
Esto va a ser una cuestión de opinión.
Mi opinión es que, a menos que tenga una buena razón para lo contrario, programe a la defensiva. Haga lo que facilitará la depuración de un error . Genera un error v_free en un puntero nulo. Algo tan simple como una assert .
void v_free(vector *v) { assert(v != NULL); free(v->data); free(v); }Considere si ignoramos silenciosamente el caso nulo. ¿La persona que llamó tenía la intención de pasar un puntero nulo o fue un error? no lo sabemos Si fue un error, el programa continúa alegremente y probablemente se bloquee misteriosamente en otro lugar. Esto hace que la depuración sea más difícil.
Considere si asumimos que v_free siempre recibirá un puntero no nulo. Si lo hace free(v->data) es un comportamiento indefinido. En el mejor de los casos, un error desordenado, en el peor, el programa continúa alegremente y probablemente se bloquee misteriosamente en otro lugar. Esto hace que la depuración sea más difícil.
Pero si proporcionamos un error, el error se detiene y se revela. Haz lo mismo para todas tus funciones vectoriales.
"Pero, ¿y si quiero pasar un puntero nulo?" Eso debería ser poco frecuente, no lo optimice. Haga que la persona que llama haga la verificación. Si realmente necesitan hacerlo con frecuencia, pueden escribir una pequeña función contenedora.
void v_free_null(vector *v) { if( v == NULL ) { return; } v_free(v); }