En C, ¿por qué algunas personas lanzan el puntero antes de liberarlo?

167

Estoy trabajando en una base de código antigua y casi todas las invocaciones de free () usan un reparto en su argumento. Por ejemplo,

free((float *)velocity);
free((float *)acceleration);
free((char *)label);

donde cada puntero es del tipo correspondiente (y coincidente). No veo ningún punto en hacer esto en absoluto. Es un código muy antiguo, así que me pregunto si es algo de K&R. Si es así, en realidad deseo admitir los compiladores antiguos que pueden haber requerido esto, por lo que no quiero eliminarlos.

¿Hay alguna razón técnica para usar estos moldes? Ni siquiera veo una gran razón pragmática para usarlos. ¿Cuál es el punto de recordarnos el tipo de datos justo antes de liberarlo?

EDITAR: Esta pregunta no es un duplicado de la otra pregunta. La otra pregunta es un caso especial de esta pregunta, que creo que es obvio si los votantes cercanos leen todas las respuestas.

Colofón: le doy a la "respuesta constante" la marca de verificación porque es una verdadera razón genuina por la que esto podría ser necesario; sin embargo, la respuesta acerca de que es una costumbre anterior a ANSI C (al menos entre algunos programadores) parece ser la razón por la que se usó en mi caso. Un montón de buenos puntos por muchas personas aquí. Gracias por sus aportaciones.

Dr. Persona Persona II
fuente
13
"¿Cuál es el punto de recordarnos el tipo de datos justo antes de liberarlo?" ¿Quizás para saber cuánta memoria se liberará?
m0skit0
12
@Codor El compilador no hace la desasignación, el sistema operativo sí.
m0skit0
20
@ m0skit0 "¿Quizás saber cuánta memoria se liberará?" El tipo no es necesario para saber cuánto liberar. Reparto por esa razón solo es mala codificación.
user694733
9
@ m0skit0 La conversión por razones de legibilidad siempre es una mala codificación, porque la conversión cambia la forma en que se interpretan los tipos y puede ocultar errores graves. Cuando se necesita legibilidad, los comentarios son mejores.
user694733
66
En la antigüedad, cuando los dinosaurios caminaban por la tierra y escribían libros de programación, creo que no existía void*en el C estándar anterior, sino solo char*. Entonces, si sus hallazgos arqueológicos revelan código que arroja el parámetro a free (), creo que debe ser de ese período o escrito por una criatura de ese momento. Sin embargo, no puedo encontrar ninguna fuente para esto, así que me abstendré de responder.
Lundin

Respuestas:

171

Es posible que se requiera la conversión para resolver las advertencias del compilador si los punteros son const. Aquí hay un ejemplo de código que causa una advertencia sin lanzar el argumento de free:

const float* velocity = malloc(2*sizeof(float));
free(velocity);

Y el compilador (gcc 4.8.3) dice:

main.c: In function main’:
main.c:9:5: warning: passing argument 1 of free discards const qualifier from pointer target type [enabled by default]
     free(velocity);
     ^
In file included from main.c:2:0:
/usr/include/stdlib.h:482:13: note: expected void *’ but argument is of type const float *’
 extern void free (void *__ptr) __THROW;

Si usa free((float*) velocity);el compilador deja de quejarse.

Manos Nikolaidis
fuente
2
@ m0skit0 que no explica por qué alguien lanzaría float*antes de liberar. Lo intenté free((void *)velocity);con gcc 4.8.3. Por supuesto, no funcionaría con un compilador antiguo
Manos Nikolaidis, del
54
Pero, ¿por qué necesitaría asignar dinámicamente memoria constante? ¡Nunca podrías usarlo!
Nils_M
33
@Nils_M es un ejemplo simplificado para hacer un punto. Lo que he hecho en el código real en una función es asignar memoria no constante, asignar valores, convertir a un puntero constante y devolverlo. Ahora, hay un puntero a la memoria constante asignada previamente que alguien tiene que liberar.
Manos Nikolaidis
2
Ejemplo : "Estas subrutinas devuelven la cadena en la memoria mal asignada recientemente, señalada por * stringValueP, que finalmente debe liberar. A veces, se declara que la función del sistema operativo que usa para liberar memoria toma un puntero a algo no constante como argumento, por lo tanto, porque * stringValueP es un puntero a una constante ”.
Carsten S
3
Errónea, si una función toma const char *pcomo argumento y luego lo libera, lo correcto a hacer no es para echar pa char*antes de llamar de forma gratuita. Es no declararlo como tomar const char *pen primer lugar, ya que modifica *p y debe declararse como corresponde. (Y si se necesita un puntero constante en lugar de un puntero para constante, int *const pno es necesario lanzar ya que en realidad es legal y, por lo tanto, funciona bien sin el yeso)
Rayo
59

Pre-estándar C no tenía void*sino solo char*, por lo que tuvo que emitir todos los parámetros pasados. Si se encuentra con el antiguo código C, por lo tanto, puede encontrar dichos modelos.

Pregunta similar con referencias .

Cuando se lanzó el primer estándar C, los prototipos para malloc y free cambiaron de tener char*a los void*que todavía tienen hoy.

Y, por supuesto, en el estándar C, tales modelos son superfluos y solo dañan la legibilidad.

Lundin
fuente
23
Pero, ¿por qué lanzarías el argumento al freemismo tipo que ya es?
jwodder
44
@chux El problema con el estándar previo es solo eso: no hay obligaciones para nada. La gente simplemente señaló el libro de K&R para el canon porque eso era lo único que tenían. Y como podemos ver en varios ejemplos en K&R 2nd edition, K&R están confundidos acerca de cómo funcionan los parámetros del parámetro freeen el estándar C (no es necesario emitir). No he leído la primera edición, así que no puedo decir si también se confundieron en los tiempos anteriores a los 80.
Lundin
77
El C estándar anterior no tenía void*, pero tampoco tenía prototipos de función, por freelo que todavía era innecesario presentar el argumento de incluso en K&R (suponiendo que todos los tipos de puntero de datos usaran la misma representación).
Ian Abbott
66
Por múltiples razones ya mencionadas en los comentarios, no creo que esta respuesta tenga sentido.
R .. GitHub DEJA DE AYUDAR AL HIELO
44
No veo cómo esta respuesta realmente respondería algo relevante. La pregunta original involucra moldes a otros tipos, no solo a char *. ¿Qué sentido tendría en viejos compiladores sin void? ¿Qué lograrían tales moldes?
AnT
34

Aquí hay un ejemplo en el que free fallaría sin un elenco:

volatile int* p = (volatile int*)malloc(5 * sizeof(int));
free(p);        // fail: warning C4090: 'function' : different 'volatile' qualifiers
free((int*)p);  // success :)
free((void*)p); // success :)

En C puede obtener una advertencia (obtuvo una en VS2012). En C ++ obtendrá un error.

Dejando a un lado casos raros, el casting simplemente hincha el código ...

Editar: lancé para void*no int*demostrar la falla. Funcionará igual que int*se convertirá void*implícitamente. int*Código agregado

egur
fuente
Tenga en cuenta que en el código publicado en la pregunta, los modelos no son para void *, sino para float *y char *. Esos moldes no son solo extraños, están equivocados.
Andrew Henle
1
La pregunta es en realidad sobre lo contrario.
m0skit0
1
No entiendo la respuesta; ¿en qué sentido free(p)fallaría? ¿Daría un error del compilador?
Codor
1
Estos son buenos puntos. Lo mismo ocurre con constlos punteros calificadores, obviamente.
Lundin
2
volatileha existido desde que C fue estandarizado, si no más. Fue no ha añadido en C99.
R .. GitHub DEJA DE AYUDAR AL HIELO
30

Razón anterior: 1. Al usar free((sometype*) ptr), el código es explícito sobre el tipo que el puntero debe considerarse como parte de la free()llamada. El reparto explícito es útil cuando free()se reemplaza con un (hágalo usted mismo) DIY_free().

#define free(ptr) DIY_free(ptr, sizeof (*ptr))

A DIY_free()era (es) una forma, especialmente en modo de depuración, para hacer análisis en tiempo de ejecución del puntero que se está liberando. Esto a menudo se combina con un DIY_malloc()para agregar sentencias, recuentos de uso de memoria global, etc. Mi grupo usó esta técnica durante años antes de que aparecieran herramientas más modernas. Obligó a que el elemento que se estaba liberando fuera lanzado al tipo que se asignó originalmente.

  1. Dadas las muchas horas dedicadas a rastrear problemas de memoria, etc., pequeños trucos como lanzar el tipo free'd ayudarían a buscar y reducir la depuración.

Moderno: evitación consty volatileadvertencias según lo abordan Manos Nikolaidis @ y @egur . Pensé que iba a notar los efectos de los 3 calificadores : const, volatile, y restrict.

[editar] Agregado char * restrict *rp2por @R .. comentario

void free_test(const char *cp, volatile char *vp, char * restrict rp, 
    char * restrict *rp2) {
  free(cp);  // warning
  free(vp);  // warning
  free(rp);  // OK
  free(rp2);  // warning
}

int main(void) {
  free_test(0,0,0,0);
  return 0;
}
chux - Restablece a Monica
fuente
3
restrictno es un problema debido a dónde está ubicado: afecta al objeto, rpno al tipo señalado. Si en cambio lo tuvieras char *restrict *rp, entonces sería importante.
R .. GitHub DEJA DE AYUDAR AL HIELO
16

Aquí hay otra hipótesis alternativa.

Se nos dice que el programa fue escrito antes de C89, lo que significa que no puede estar trabajando en algún tipo de desajuste con el prototipo de free, porque no solo no había tal cosa constni void *antes de C89, no había tal cosa como Un prototipo de función anterior a C89. stdlib.hen sí fue un invento del comité. Si los encabezados del sistema se molestaran en declarar free, lo habrían hecho así:

extern free();  /* no `void` return type either! */

Ahora, el punto clave aquí es que la ausencia de prototipos de funciones significa que el compilador no realizó ninguna comprobación de tipo de argumento . Aplicó las promociones de argumento por defecto (las mismas que todavía se aplican a las llamadas a funciones variadas) y eso fue todo. La responsabilidad de hacer que los argumentos en cada sitio de llamadas se alineen con las expectativas del destinatario recae completamente en el programador.

Sin embargo, esto todavía no significa que fuera necesario presentar el argumento freeen la mayoría de los compiladores de K&R. Una función como

free_stuff(a, b, c)
    float *a;
    char *b;
    int *c;
{
    free(a);
    free(b);
    free(c);
}

debería haber sido compilado correctamente. Así que creo que lo que tenemos aquí es un programa escrito para hacer frente a un compilador con errores para un entorno inusual: por ejemplo, un entorno donde sizeof(float *) > sizeof(int)y el compilador no usaría la convención de llamada apropiada para punteros a menos que los eches en el punto de la llamada.

No conozco ningún entorno de ese tipo, pero eso no significa que no haya ninguno. Los candidatos más probables que vienen a la mente son los compiladores "C minúsculos" reducidos para micros de 8 y 16 bits a principios de la década de 1980. Tampoco me sorprendería saber que Crays temprano tuvo problemas como este.

zwol
fuente
1
La primera mitad estoy totalmente de acuerdo. Y la segunda mitad es una conjetura intrigante y plausible.
chux - Restablece a Mónica el