¿Cómo funcionan malloc() y free()?
Quiero saber como malloc
y free
trabajar.
int main() {
unsigned char *p = (unsigned char*)malloc(4*sizeof(unsigned char));
memset(p,0,4);
strcpy((char*)p,"abcdabcd"); // **deliberately storing 8bytes**
cout << p;
free(p); // Obvious Crash, but I need how it works and why crash.
cout << p;
return 0;
}
Estaría muy agradecido si la respuesta fuera profunda a nivel de memoria, si es posible.
Bien, ya se publicaron algunas respuestas sobre malloc.
La parte más interesante es cómo funciona free (y en esta dirección, también se puede entender mejor malloc).
En muchas implementaciones de malloc/free, free normalmente no devuelve la memoria al sistema operativo (o al menos sólo en casos raros). La razón es que tendrás huecos en tu montón y, por lo tanto, puede suceder que termines tus 2 o 4 GB de memoria virtual con huecos. Esto debe evitarse, ya que tan pronto como se acabe la memoria virtual, estarás en un gran problema. La otra razón es que el sistema operativo solo puede manejar fragmentos de memoria que tienen un tamaño y una alineación específicos. Para ser específico: normalmente, el sistema operativo solo puede manejar bloques que el administrador de memoria virtual puede manejar (la mayoría de las veces, múltiplos de 512 bytes, por ejemplo, 4 KB).
Por lo tanto, devolver 40 bytes al sistema operativo simplemente no funcionará. Entonces, ¿qué hace lo gratuito?
Free pondrá el bloque de memoria en su propia lista de bloques libres. Normalmente también intenta fusionar bloques adyacentes en el espacio de direcciones. La lista de bloqueo libre es solo una lista circular de fragmentos de memoria que tienen algunos datos administrativos al principio. Esta es también la razón por la que gestionar elementos de memoria muy pequeños con el estándar malloc/free no es eficiente. Cada fragmento de memoria necesita datos adicionales y con tamaños más pequeños se produce una mayor fragmentación.
La lista libre es también el primer lugar que analiza malloc cuando se necesita una nueva porción de memoria. Se escanea antes de solicitar nueva memoria del sistema operativo. Cuando se encuentra un fragmento que es mayor que la memoria necesaria, se divide en dos partes. Uno se devuelve a la persona que llama y el otro se vuelve a colocar en la lista libre.
Hay muchas optimizaciones diferentes para este comportamiento estándar (por ejemplo, para pequeñas porciones de memoria). Pero dado que malloc y free deben ser tan universales, el comportamiento estándar es siempre el recurso alternativo cuando las alternativas no son utilizables. También hay optimizaciones en el manejo de la lista libre, por ejemplo, almacenar los fragmentos en listas ordenadas por tamaño. Pero todas las optimizaciones también tienen sus propias limitaciones.
¿Por qué falla su código?
La razón es que al escribir 9 caracteres (no olvide el byte nulo final) en un área de 4 caracteres, probablemente sobrescribirá los datos administrativos almacenados para otra porción de memoria que reside "detrás" de su porción de datos ( ya que estos datos suelen almacenarse "delante" de los fragmentos de memoria). Cuando free intenta poner su fragmento en la lista libre, puede tocar estos datos administrativos y, por lo tanto, tropezar con un puntero sobrescrito. Esto colapsará el sistema.
Este es un comportamiento bastante elegante. También he visto situaciones en las que un puntero fuera de control en algún lugar sobrescribió datos en la lista de memoria libre y el sistema no falló inmediatamente, sino algunas subrutinas más tarde. Incluso en un sistema de complejidad media, estos problemas pueden ser realmente difíciles de depurar. En el único caso en el que estuve involucrado, nos tomó (un grupo más grande de desarrolladores) varios días encontrar el motivo del bloqueo, ya que estaba en una ubicación totalmente diferente a la indicada en el volcado de memoria. Es como una bomba de tiempo. Ya sabes, tu próximo "gratis" o "malloc" fallará, ¡pero no sabes por qué!
Estos son algunos de los peores problemas de C/C++ y una de las razones por las que los punteros pueden ser tan problemáticos.
Como dice un usuario en este hilo del foro :
Su proceso tiene una región de memoria, desde la dirección x hasta la dirección y, llamada montón. Todos sus datos malloc'd viven en esta área. malloc() mantiene alguna estructura de datos, digamos una lista, de todos los fragmentos de espacio libres en el montón. Cuando llama a malloc, busca en la lista un fragmento que sea lo suficientemente grande para usted, le devuelve un puntero y registra el hecho de que ya no está libre, así como su tamaño. Cuando llamas a free() con el mismo puntero, free() busca qué tan grande es ese fragmento y lo agrega nuevamente a la lista de fragmentos libres(). Si llama a malloc() y no puede encontrar ningún fragmento lo suficientemente grande en el montón, utiliza la llamada al sistema brk() para hacer crecer el montón, es decir, aumenta la dirección y y hace que todas las direcciones entre la y antigua y la nueva y ser memoria válida. brk() debe ser una llamada al sistema; no hay forma de hacer lo mismo completamente desde el espacio de usuario.
malloc() depende del sistema/compilador, por lo que es difícil dar una respuesta específica. Básicamente, sin embargo, realiza un seguimiento de la memoria asignada y, dependiendo de cómo lo haga, sus llamadas gratuitas podrían fallar o tener éxito.
malloc() and free() don't work the same way on every O/S.
Una implementación de malloc/free hace lo siguiente:
- Obtenga un bloque de memoria del sistema operativo a través de sbrk() (llamada Unix).
- Cree un encabezado y un pie de página alrededor de ese bloque de memoria con información como el tamaño, los permisos y dónde están el bloque anterior y siguiente.
- Cuando llega una llamada a malloc, se hace referencia a una lista que apunta a bloques del tamaño apropiado.
- Luego se devuelve este bloque y los encabezados y pies de página se actualizan en consecuencia.
La protección de la memoria tiene granularidad de página y requeriría interacción con el kernel
Su código de ejemplo esencialmente pregunta por qué el programa de ejemplo no captura, y la respuesta es que la protección de la memoria es una característica del kernel y se aplica solo a páginas enteras, mientras que el asignador de memoria es una característica de la biblioteca y administra... sin aplicación... arbitraria bloques de tamaño que a menudo son mucho más pequeños que las páginas.
La memoria sólo se puede eliminar de su programa en unidades de páginas, e incluso eso es poco probable que se observe.
calloc(3) y malloc(3) interactúan con el kernel para obtener memoria, si es necesario. Pero la mayoría de las implementaciones de free(3) no devuelven memoria al kernel 1 , simplemente la agregan a una lista libre que calloc() y malloc() consultarán más adelante para reutilizar los bloques liberados.
Incluso si free() quisiera devolver memoria al sistema, necesitaría al menos una página de memoria contigua para que el kernel realmente proteja la región, por lo que liberar un pequeño bloque solo conduciría a un cambio de protección si fuera el último bloque pequeño de una página.
Entonces su bloque está ahí, en la lista libre. Casi siempre puedes acceder a él y a la memoria cercana como si todavía estuviera asignado. C se compila directamente en código de máquina y, sin arreglos especiales de depuración, no hay controles de integridad en las cargas y almacenes. Ahora, si intenta acceder a un bloque libre, el comportamiento no está definido por el estándar para no hacer demandas irrazonables a los implementadores de la biblioteca. Si intenta acceder a la memoria liberada o fuera de un bloque asignado, hay varias cosas que pueden salir mal:
- A veces, los asignadores mantienen bloques de memoria separados, a veces usan un encabezado que asignan justo antes o después (un "pie de página", supongo) de su bloque, pero es posible que quieran usar memoria dentro del bloque con el fin de mantener la lista libre. Unidos entre sí. Si es así, leer el bloque está bien, pero su contenido puede cambiar y escribir en el bloque probablemente provocaría que el asignador se comportara mal o fallara.
- Naturalmente, su bloque puede asignarse en el futuro, y luego es probable que su código o una rutina de biblioteca lo sobrescriba, o con ceros mediante calloc().
- Si el bloque se reasigna, es posible que también se cambie su tamaño, en cuyo caso se escribirán aún más enlaces o inicializaciones en varios lugares.
- Obviamente, puede hacer referencia tan fuera de rango que cruce el límite de uno de los segmentos conocidos del núcleo de su programa, y en este caso quedará atrapado.
teoría de operación
Entonces, retrocediendo desde su ejemplo hasta la teoría general, malloc(3) obtiene memoria del kernel cuando la necesita y generalmente en unidades de páginas. Estas páginas se dividen o consolidan según lo requiera el programa. Malloc y free cooperan para mantener un directorio. Fusionan bloques libres adyacentes cuando es posible para poder proporcionar bloques grandes. El directorio puede implicar o no el uso de la memoria en bloques liberados para formar una lista vinculada. (La alternativa es un poco más compatible con la memoria compartida y la paginación, e implica asignar memoria específicamente para el directorio). Malloc y free tienen poca o ninguna capacidad para imponer el acceso a bloques individuales, incluso cuando se compila código de depuración especial y opcional en el programa.
1. El hecho de que muy pocas implementaciones de free() intenten devolver memoria al sistema no se debe necesariamente a que los implementadores se relajen. Interactuar con el kernel es mucho más lento que simplemente ejecutar el código de la biblioteca y el beneficio sería pequeño. La mayoría de los programas tienen una huella de memoria en estado estable o en aumento, por lo que el tiempo dedicado a analizar el montón en busca de memoria retornable sería completamente desperdiciado. Otras razones incluyen el hecho de que la fragmentación interna hace que sea poco probable que existan bloques alineados con la página, y es probable que devolver un bloque fragmente los bloques a ambos lados. Finalmente, es probable que los pocos programas que devuelven grandes cantidades de memoria omitan malloc() y simplemente asignen y liberen páginas de todos modos.