c++ c

c++ - ¿Por qué se bloquea la libre cuando se llama dos veces?



(5)

En C y C ++, free(my_pointer) bloquea cuando se llama dos veces.

¿Por qué? Hay contabilidad de cada malloc junto con el tamaño. Cuando se llama al primer servicio free , se identifica que se asignó con qué tamaño, por eso no debemos pasar el tamaño junto con la llamada gratuita.

Ya que sabe todo, ¿por qué no revisa la segunda vez y no hace nada?

O no entiendo malloc/free comportamiento de malloc/free o free no se implementa de forma segura.


¿por qué no verifica la segunda vez cuando no puede encontrar ningún tamaño asignado para la segunda llamada gratuita ()?

Una comprobación adicional en la función free() sí misma ralentizaría su programa en todos los casos correctos. No deberías estar haciendo un doble gratis. Manejar la memoria es tu responsabilidad como programador; El no hacerlo es un error de programación. Es parte de la filosofía de C: te da todo el poder que necesitas, pero como consecuencia, te facilita dispararte en el pie.

Muchos tiempos de ejecución de C harán algunas comprobaciones en sus versiones de depuración, por lo que recibirá una notificación razonable en caso de que esté haciendo algo mal.


Buena pregunta. Como se puede observar, malloc y free suelen realizar algún tipo de contabilidad, a menudo en los pocos bytes que preceden a la asignación. Pero piensa de esta manera:

  1. Malloc algo de memoria - agrega los datos de contabilidad.
  2. Liberarlo - la memoria se devuelve a la agrupación.
  3. Usted u otra persona de malloc tiene más memoria, que podría o no incluir o alinearse con la asignación anterior.
  4. Liberas el puntero viejo de nuevo.

En este punto, el montón (el código para malloc y una gestión gratuita) ya ha perdido la pista y / o ha sobrescrito los datos de contabilidad, ¡porque la memoria ha vuelto al montón!

De ahí los choques. La única forma de proporcionar esto sería recordar cada asignación realizada en una base de datos en algún lugar, que crecería sin límites. Así que ellos no hacen eso. En su lugar, recuerda no hacer doble libre. :)


No se le permite llamar free a la memoria no asignada, el estándar establece que claramente (parafraseado ligeramente, mi énfasis):

La función free hace que el espacio señalado por su argumento se desasigne, es decir, se ponga a disposición para su posterior asignación. Si el argumento es un puntero nulo, no se produce ninguna acción. De lo contrario, si el argumento no coincide con un puntero anteriormente devuelto por una función de administración de memoria, o si el espacio ha sido desasignado por una llamada a free o realloc, el comportamiento no está definido.

¿Qué sucede, por ejemplo, si la dirección que está liberando dos veces se ha reasignado en medio de un nuevo bloque y el código que lo asignó simplemente almacenó algo allí que parecía un verdadero encabezado de bloque malloc? Me gusta:

+- New pointer +- Old pointer v v +------------------------------------+ | <Dodgy bit> | +------------------------------------+

Caos, eso es lo que.

Las funciones de asignación de memoria son una herramienta como una motosierra y, si las utiliza correctamente, no debería tener problemas. Sin embargo, si los usas mal, las consecuencias son tu culpa, ya sea corromper la memoria o algo peor, o cortarte uno de tus brazos :-)

Y en cuanto al comentario:

... se puede comunicar con gracia al usuario final sobre la duplicación de la misma ubicación.

Aparte de mantener un registro de todas free llamadas free y de malloc para asegurarte de no hacer un doble de bloque gratuito, no puedo ver que esto sea viable. Requeriría una gran sobrecarga y aún así no solucionaría todos los problemas.

Que pasaria si:

  • Hilo A memoria asignada y liberada en la dirección 42.
  • el hilo B asignó a la memoria una dirección 42 y comenzó a usarla.
  • El hilo A liberó esa memoria por segunda vez.
  • el hilo C asignó a la memoria una dirección 42 y comenzó a usarla.

Luego, los subprocesos B y C piensan que son dueños de esa memoria (no tienen que ser subprocesos de ejecución, estoy usando el término subproceso aquí como una pieza de código que se ejecuta, todo podría estar en el único subproceso de ejecución pero se llama secuencialmente).

No, creo que el malloc actual y free están bien siempre que los uses correctamente. De todos modos, piense en implementar su propia versión, no veo nada de malo en eso, pero sospecho que se encontrará con algunos problemas de rendimiento bastante espinosos.

Si desea implementar su propio envoltorio de forma free , puede hacerlo más seguro (al costo de un pequeño impacto en el rendimiento), específicamente con algo como las llamadas myFreeXxx a continuación:

#include <stdio.h> #include <stdlib.h> void myFreeVoid (void **p) { free (*p); *p = NULL; } void myFreeInt (int **p) { free (*p); *p = NULL; } void myFreeChar (char **p) { free (*p); *p = NULL; } int main (void) { char *x = malloc (1000); printf ("Before: %p/n", x); myFreeChar (&x); printf ("After: %p/n", x); return 0; }

El resultado del código es que puede llamar a myFreeXxx con un puntero a su puntero y ambos:

  • liberar la memoria; y
  • establece el puntero a NULL.

Ese último bit significa que, si intenta liberar el puntero de nuevo, no hará nada (porque la liberación de NULL está específicamente cubierta por el estándar).

No lo protegerá de todas las situaciones, por ejemplo, si hace una copia del puntero en otro lugar, libere el original y luego libere la copia:

char *newptr = oldptr; myFreeChar (&oldptr); // frees and sets to NULL. myFreeChar (&newptr); // double-free because it wasn''t set to NULL.

Si está preparado para usar C11, hay una manera mejor de tener que llamar explícitamente a una función diferente para cada tipo ahora que C tiene una sobrecarga de funciones de compilación. Puede usar selecciones genéricas para llamar a la función correcta y al mismo tiempo permitir la seguridad de tipos:

#include <stdio.h> #include <stdlib.h> void myFreeVoid (void **p) { free (*p); *p = NULL; } void myFreeInt (int **p) { free (*p); *p = NULL; } void myFreeChar (char **p) { free (*p); *p = NULL; } #define myFree(x) _Generic((x), / int** : myFreeInt, / char**: myFreeChar, / default: myFreeVoid )(x) int main (void) { char *x = malloc (1000); printf ("Before: %p/n", x); myFree (&x); printf ("After: %p/n", x); return 0; }

Con eso, simplemente llame a myFree y seleccionará la función correcta según el tipo.


Podrías estar malinterpretando su comportamiento. Si se bloquea de inmediato, se implementa de manera segura. Puedo atestiguar que esto no fue un comportamiento común de forma gratuita () hace muchas lunas. La implementación típica de CRT en ese entonces no hizo ninguna comprobación. Rápido y furioso, simplemente corrompería la estructura interna del montón, arruinando las cadenas de asignación.

Sin ningún tipo de diagnóstico, el programa se comportaría mal o se bloquearía mucho después de que se produjera la corrupción del montón. Sin tener ningún indicio de por qué se comportó mal, el código que se estrelló no fue realmente responsable del accidente. Un Heisenbug, muy difícil de solucionar.

Esto ya no es común para las implementaciones modernas de pila CRT o OS. Este tipo de comportamiento indefinido es muy explotable por el malware. Y hace que tu vida sea más fácil, no encontrarás ningún error en tu código. Me ha mantenido fuera de problemas durante los últimos años, no he tenido que depurar la corrupción del montón no rastreable en mucho tiempo. Buena cosa.


Tu dices:

No entendí por qué. Hay contabilidad de cada malloc () junto con el tamaño.

No es necesario. Explicaré un poco sobre dlmalloc (usado en glibc, uClibc, ...).

Dlmalloc rastrea bloques de espacio libre. No puede haber dos bloques libres contiguos, se fusionan de inmediato. ¡Los bloques asignados no se rastrean en absoluto! Los bloques asignados tienen algún espacio libre para información de contabilidad (tamaño de este bloque, tamaño del bloque anterior y algunas banderas). Cuando un bloque asignado es free () ''d, dlmalloc lo inserta en una lista con doble enlace.

Por supuesto, todo esto se explica mejor en este artículo de dlmalloc.