2010-06-25 12 views
16

En C y C++, free(my_pointer) se bloquea cuando se llama dos veces.Por qué se producen bloqueos gratis cuando se llama dos veces

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

Como sabe todo, ¿por qué no se comprueba la segunda vez y no hace nada?

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

+1

¿Se puede publicar un fragmento de código donde se produce el error? – ChadNC

+1

¿Por qué el voto a favor? No estoy seguro de si es una estafa, pero esta es una muy buena pregunta. –

+3

C es un lenguaje que valora la velocidad y el control del programador sobre la seguridad garantizada. Se espera que desarrolles hábitos que te mantengan a salvo en lugar de hacer cosas al azar y esperar ser atrapado.Incluso si fuera posible hacer la contabilidad que desea, sería más lento, y no quiero pagar el precio de velocidad para mantener a otros desarrolladores (los tipos de soporte tienden a pensar que siempre son otros desarrolladores que necesitan redes de seguridad). . –

Respuesta

27

No se permiten llamar free en la memoria no asignada, la norma indica que con toda claridad (ligeramente parafraseados, el subrayado es mío):

La función free hace que el espacio apuntado por su argumento para cancelar la asignación, es decir, disponible para una asignación adicional. Si el argumento es un puntero nulo, no se produce ninguna acción. De lo contrario, si el argumento no coincide con un puntero devuelto anteriormente por una función de gestión de memoria, o si el espacio ha sido desasignado por una llamada a free o realloc, el comportamiento es indefinido.

¿Qué ocurre, por ejemplo, si la dirección que es de doble liberación se ha reasignado en medio de un nuevo bloque y el código que asigna que acaba de pasar a almacenar algo allí que parecía una verdadera malloc -block encabezado? Me gusta:

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

Caos, eso es qué.

Las funciones de asignación de memoria son una herramienta como una motosierra y, si las usa correctamente, no debería tener problemas. Si se les hace mal uso, sin embargo, las consecuencias son su propia culpa, ya sea de memoria que se dañe o peor, o cortar uno de sus brazos :-)


Y respecto al comentario:

.. .puede comunicarse con gracia al usuario final sobre la duplicación de la misma ubicación libre.

corto de mantener un registro de todas las llamadas malloc y free para asegurarse de que no lo haga doble libre de un bloque, no puedo ver esto como siendo viable. Se requeriría una gran sobrecarga y todavía no solucionaría todos los problemas.

¿Qué pasaría si:

  • hilo Una asignado y liberado memoria en la dirección 42.
  • hilo B memoria asignada una dirección 42 y empezado a utilizar.
  • thread A liberó esa memoria por segunda vez.
  • hilo C asignó a la memoria una dirección 42 y comenzó a usarla.

Luego tiene los hilos B y C, ambos pensando que son dueños de esa memoria (no tienen que ser hilos de ejecución, estoy usando el término hilo aquí como una pieza de código que se ejecuta, podría todos están en el único hilo de ejecución pero se llaman secuencialmente).

No, creo que la actual malloc y free están bien siempre que las use correctamente. Por supuesto 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 hace quieren poner en práctica su propia envoltura alrededor free, puede que sea más seguro (a costa de un pequeño impacto de rendimiento), específicamente con algo así como los myFreeXxx llamadas siguientes:

#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 se puede llamar myFreeXxx con un puntero al puntero y lo hará tanto:

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

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

Se se no le protegerá de todas las situaciones, como por ejemplo si se hace una copia del puntero en otro lugar, liberar la original, y luego liberar la copia:

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

Si' Para usar C11, hay una manera mejor que tener que llamar explícitamente a una función diferente para cada tipo ahora que C tiene una sobrecarga de función de tiempo de compilación. Se puede utilizar selecciones genéricas a llamar a la función correcta al tiempo que permite 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, sólo tiene que llamar myFree y va a seleccionar la función correcta en función del tipo.

+0

eso es cierto ... no podemos llamar gratis dos veces ... pero veo un alcance de mejora en la implementación gratuita() antes que en la falla ... se puede comunicar con agrado al usuario final sobre la duplicación de la misma ubicación libre que tiene contabilidad ... ¿estoy en lo correcto? –

+0

Dudo que haya suficiente contabilidad en la mayoría de las implementaciones (ciertamente no las que he visto) para garantizar esa operación. Y si presenta suficiente, es casi seguro que tendrá implicaciones de rendimiento. Vea la actualización, pero la sinopsis básica es que los enfoques actuales equilibran la velocidad y la seguridad. – paxdiablo

+0

Y aparte del posible impacto en el rendimiento, en el momento en que algo desagradable sucede con las estructuras de datos del montón, el tiempo de ejecución de C debe abortar antes de dejar que algún malware se complique con las rutas de ejecución del código. – ninjalj

3

por qué no se compruebe segunda vez cuando no puede encontrado cualquier tamaño asignado para el segundo libre() llama a

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

Muchos C runtimes verifican sus versiones de depuración, por lo que recibirás una notificación razonable en caso de que estés haciendo algo mal.

3

Buena pregunta. Como nota, malloc y free suelen hacer algún tipo de contabilidad, a menudo en los pocos bytes que preceden a la asignación.Pero piénselo de esta manera:

  1. Malloc un poco de memoria - agrega los datos de contabilidad.
  2. Liberarlo: la memoria se devuelve al grupo.
  3. Usted o alguien más malloc tiene más memoria, que podría o no incluir o alinearse con la asignación anterior.
  4. Libera el puntero anterior nuevamente.

El montón (el código para malloc una gestión gratuita) tiene en este punto ya perdió la pista y/o sobrescribió los datos de contabilidad, porque la memoria ha vuelto al montón!

De ahí los bloqueos. La única forma de proporcionar esto sería recordando cada asignación alguna vez hecha en una base de datos en alguna parte, que crecería sin límites. Entonces ellos no lo hacen. En cambio, recuerda no duplicar. :)

+0

"El montón (el código para malloc una gestión gratuita) tiene en este momento perdido la pista y/o sobrescrito los datos de contabilidad, porque la memoria ha vuelto al montón!" ... como dijiste ... una vez Se ha perdido la información ... se puede concluir que ya está libre() o dirección incorrecta. y comunicar al programador con gracia ..pero no debería intentar liberarlo de esa ubicación ... y falla la aplicación ... porque no ha corrompido nada en el montón ... simplemente envió un puntero incorrecto que ya ha sido validado con los detalles de la contabilidad y no encontró ninguna referencia al mismo ... –

+1

¡Ah! Pero si la memoria devuelta ya ha sido reutilizada por otra persona, entonces lo que * debería * estar en el área de contabilidad ha sido reemplazado por los datos de otra persona, luego lee de forma gratuita esa información y asume que se refiere al seguimiento de montón ... cuando de hecho, no son datos de contabilidad en absoluto. Así que libre se va a las malezas y los accidentes. –

+0

"Así que gratis se va a las malas hierbas y se bloquea". O algo peor. Un atacante creó la información de contabilidad para que free() termine sobrescribiendo algún puntero importante, como el SEH o la siguiente función dinámica a ser llamada. – ninjalj

1

Usted dice:

no entiende 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 inmediatamente. ¡Los bloques asignados no son rastreados en absoluto! Los bloques asignados tienen espacio libre para la información de contabilidad (tamaño de este bloque, tamaño del bloque anterior y algunos indicadores). Cuando un bloque asignado es libre() 'd, dlmalloc lo inserta en una lista doblemente enlazada.

Por supuesto, todo esto se explica mejor en this dlmalloc article

9

Usted podría estar malinterpretando su comportamiento. Si se bloquea de inmediato, entonces es implementado de manera segura. Puedo atestiguar que este no era un comportamiento común de forma gratuita() hace muchas lunas. La implementación típica de CRT en aquel entonces no se comprobó en absoluto. 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 colgaría mucho después de que se produjera la corrupción del montón. Sin tener ninguna pista por qué se comportó mal de esa manera, el código que se estrelló no fue en realidad responsable del bloqueo. Un heisenbug, muy difícil de solucionar.

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

Cuestiones relacionadas