c++ - como - malloc sizeof
¿Cómo funcionan malloc() y free()? (14)
Quiero saber cómo funciona malloc
y free
trabajo free
.
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 es profunda a nivel de memoria, si es posible.
La protección de la memoria tiene granularidad de página y requeriría la interacción del núcleo.
El código de ejemplo esencialmente pregunta por qué el programa de ejemplo no se intercepta, y la respuesta es que la protección de la memoria es una característica del núcleo y se aplica solo a páginas enteras, mientras que el asignador de memoria es una característica de la biblioteca y se administra ... sin cumplimiento ... arbitrario bloques de tamaño que a menudo son mucho más pequeños que las páginas.
La memoria solo puede eliminarse de su programa en unidades de página, e incluso eso es poco probable que se observe.
Calloc (3) y malloc (3) interactúan con el núcleo para obtener memoria, si es necesario. Pero la mayoría de las implementaciones de free (3) no devuelven la memoria al kernel 1 , solo la agregan a una lista gratuita que calloc () y malloc () consultarán más adelante para reutilizar los bloques liberados.
Incluso si un free () quisiera devolver la memoria al sistema, necesitaría al menos una página de memoria contigua para que el kernel proteja la región, por lo que liberar un pequeño bloque solo llevaría a un cambio de protección si fuera El último bloque pequeño en una página.
Así que tu bloque está ahí, sentado en la lista libre. Casi siempre se puede acceder a ella y a la memoria cercana como si todavía estuviera asignada. C compila directamente al código de la 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 bibliotecas. Si intenta acceder a la memoria o memoria liberada 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 de (un "pie de página", supongo) su bloque, pero tal vez quieran usar la memoria dentro del bloque con el fin de mantener la lista libre Unidos entre sí. Si es así, su lectura del bloque está bien, pero su contenido puede cambiar, y la escritura en el bloque podría causar que el asignador se comporte mal o se bloquee.
- Naturalmente, su bloqueo puede asignarse en el futuro, y luego es probable que se sobrescriba con su código o una rutina de biblioteca, o con ceros con calloc ().
- Si el bloque se reasigna, también se puede cambiar su tamaño, en cuyo caso se escribirán más enlaces o inicializaciones en varios lugares.
- Obviamente, puede hacer referencia tan fuera del alcance que cruce un límite de uno de los segmentos conocidos del kernel de su programa, y en este caso quedará atrapado.
teoría de operación
Entonces, trabajando al revés de su ejemplo a la teoría general, malloc (3) obtiene memoria del núcleo cuando lo necesita, y normalmente en unidades de página. Estas páginas están divididas o consolidadas según lo requiera el programa. Malloc y free cooperan para mantener un directorio. Se combinan bloques libres adyacentes cuando es posible para poder proporcionar bloques grandes. El directorio puede o no involucrar el uso de la memoria en bloques liberados para formar una lista vinculada. (La alternativa es un poco más de memoria compartida y de fácil uso de la paginación, e implica la asignación de 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 un código de depuración especial y opcional. el programa.
1. El hecho de que muy pocas implementaciones de free () intenten devolver la memoria al sistema no se debe necesariamente a que los implementadores estén desactivados. Interactuar con el kernel es mucho más lento que simplemente ejecutar código de biblioteca, y el beneficio sería pequeño. La mayoría de los programas tienen una huella de memoria constante o en estado estable, por lo que el tiempo dedicado a analizar el montón en busca de memoria retornable se perdería por completo. Otras razones incluyen el hecho de que la fragmentación interna hace que sea improbable que existan bloques alineados con páginas, y es probable que devolver un bloque fragmentaría los bloques a ambos lados. Finalmente, es probable que los pocos programas que devuelven grandes cantidades de memoria omitan a malloc () y simplemente asignen y liberen páginas de todos modos.
Bueno, depende de la implementación del asignador de memoria y del sistema operativo.
En Windows, por ejemplo, un proceso puede solicitar una página o más de RAM. El SO luego asigna esas páginas al proceso. Esto no es, sin embargo, la memoria asignada a su aplicación. El asignador de memoria CRT marcará la memoria como un bloque contiguo "disponible". El asignador de memoria CRT luego pasará por la lista de bloques libres y encontrará el bloque más pequeño posible que pueda usar. Luego tomará todo el bloque que necesite y lo agregará a una lista "asignada". Adjunto al encabezado de la asignación de memoria real habrá un encabezado. Este encabezado contendrá varios bits de información (podría, por ejemplo, contener los bloques asignados anterior y siguiente para formar una lista enlazada. Probablemente contendrá el tamaño de la asignación).
Libre eliminará el encabezado y lo volverá a agregar a la lista de memoria libre. Si forma un bloque más grande con los bloques libres que lo rodean, estos se sumarán para dar un bloque más grande. Si ahora toda una página está libre, el asignador, lo más probable, devolverá la página al sistema operativo.
No es un problema simple. La parte del asignador del sistema operativo está completamente fuera de su control. Le recomiendo que lea algo como el Malloc de Doug Lea (DLMalloc) para comprender cómo funcionará un asignador bastante rápido.
Editar: Su bloqueo se debe al hecho de que al escribir más grande que la asignación, ha sobrescrito el siguiente encabezado de memoria. De esta manera, cuando se libera, se confunde mucho en cuanto a qué es exactamente lo que está liberando y cómo fusionarse en el siguiente bloque. Esto no siempre puede causar un accidente de inmediato en el libre. Puede causar un accidente más adelante. En general, evite sobrescribir la memoria!
Como aluser dice en este hilo del foro :
Su proceso tiene una región de memoria, desde la dirección x a la dirección y, llamada el montón. Todos sus datos malloc''d viven en esta área. malloc () mantiene cierta estructura de datos, digamos una lista, de todos los trozos libres de espacio en el montón. Cuando llamas a malloc, busca en la lista un trozo que sea lo suficientemente grande para ti, le devuelve un puntero y registra el hecho de que ya no es gratis, ni qué tan grande es. Cuando llamas a free () con el mismo puntero, free () busca qué tan grande es ese trozo y lo agrega de nuevo a la lista de trozos libres (). Si llama a malloc () y no puede encontrar una porción lo suficientemente grande en el montón, utiliza el syscall brk () para aumentar el montón, es decir, aumentar la dirección y y causar que todas las direcciones entre la antigua y y la nueva y para ser valida la memoria. brk () debe ser un syscall; no hay manera 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 que está asignada y dependiendo de cómo lo haga, por lo que las llamadas gratuitas podrían fallar o tener éxito.
malloc() and free() don''t work the same way on every O/S.
El funcionamiento de malloc () y free () depende de la biblioteca de tiempo de ejecución utilizada. En general, malloc () asigna un montón (un bloque de memoria) del sistema operativo. Cada solicitud a malloc () luego asigna una pequeña parte de esta memoria devolviendo un puntero a la persona que llama. Las rutinas de asignación de memoria tendrán que almacenar alguna información adicional sobre el bloque de memoria asignado para poder realizar un seguimiento de la memoria usada y libre en el montón. Esta información a menudo se almacena en unos pocos bytes justo antes del puntero devuelto por malloc () y puede ser una lista enlazada de bloques de memoria.
Al escribir más allá del bloque de memoria asignado por malloc (), lo más probable es que destruya parte de la información de contabilidad del siguiente bloque, que puede ser el bloque de memoria no utilizado restante.
Un lugar donde el programa también puede fallar es al copiar demasiados caracteres en el búfer. Si los caracteres adicionales se encuentran fuera del montón, es posible que obtenga una infracción de acceso mientras intenta escribir en una memoria no existente.
En teoría, malloc obtiene memoria del sistema operativo para esta aplicación. Sin embargo, dado que es posible que solo desee 4 bytes, y el sistema operativo debe funcionar en páginas (a menudo 4k), malloc hace un poco más que eso. Toma una página y coloca su propia información allí para que pueda realizar un seguimiento de lo que ha asignado y liberado de esa página.
Cuando asignas 4 bytes, por ejemplo, malloc te da un puntero a 4 bytes. Lo que quizás no se dé cuenta es que malloc usa la memoria de 8-12 bytes antes de sus 4 bytes para formar una cadena de toda la memoria que ha asignado. Cuando llamas gratis, toma tu puntero, retrocede hasta donde están los datos y funciona con eso.
Cuando liberas memoria, malloc quita ese bloque de memoria de la cadena ... y puede devolver esa memoria al sistema operativo. Si lo hace, el acceso a esa memoria probablemente fallará, ya que el sistema operativo le quitará los permisos para acceder a esa ubicación. Si malloc conserva la memoria (debido a que tiene otras cosas asignadas en esa página, o para alguna optimización), el acceso funcionará. Todavía está mal, pero podría funcionar.
DESCARGO DE RESPONSABILIDAD: Lo que describí es una implementación común de malloc, pero de ninguna manera es la única posible.
Es difícil decirlo porque el comportamiento real es diferente entre diferentes compiladores / tiempos de ejecución. Incluso las versiones debug / release tienen comportamientos diferentes. Las versiones de depuración de VS2005 insertarán marcadores entre las asignaciones para detectar daños en la memoria, por lo que en lugar de una falla, se activará en free ().
Esto no tiene nada que ver específicamente con malloc y gratis. Su programa muestra un comportamiento indefinido después de que copia la cadena, ya que podría fallar en ese punto o en cualquier momento posterior. Esto sería cierto incluso si nunca usó malloc y gratis, y asignó la matriz char en la pila o estáticamente.
Hay una implementación de muestra de malloc()
y free()
en The Book (Kernighan and Ritchie " The C Programming Language "). Como tenía que preguntar, no lo ha leído, vaya y léalo, y arrepiéntase de sus maneras pecaminosas. :RE
Malloc y free son dependientes de la implementación. Una implementación típica implica la partición de la memoria disponible en una "lista libre", una lista vinculada de bloques de memoria disponibles. Muchas implementaciones lo dividen artificialmente en objetos pequeños contra grandes. Los bloques libres comienzan con información acerca de qué tan grande es el bloque de memoria y dónde está el siguiente, etc.
Cuando haces malloc, se saca un bloque de la lista libre. Cuando se libera, el bloqueo se vuelve a poner en la lista libre. Lo más probable es que cuando sobrescriba el final de su puntero, esté escribiendo en el encabezado de un bloque en la lista libre. Cuando liberas tu memoria, free () intenta mirar el siguiente bloque y probablemente termina golpeando un puntero que causa un error de bus.
OK algunas respuestas sobre malloc ya fueron publicadas.
La parte más interesante es cómo funciona el trabajo libre (y en esta dirección, malloc también se puede entender mejor).
En muchas implementaciones de malloc / free, free no devuelve la memoria al sistema operativo (o al menos solo en casos raros). La razón es que obtendrá huecos en su montón y, por lo tanto, puede suceder, que acaba de terminar sus 2 o 4 GB de memoria virtual con huecos. Esto debe evitarse, ya que tan pronto como se termine la memoria virtual, estará en un gran problema. La otra razón es que el sistema operativo solo puede manejar trozos de memoria que son de un tamaño y 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 son múltiplos de 512 bytes, por ejemplo, 4 KB).
Así que devolver 40 bytes al sistema operativo simplemente no funcionará. Entonces, ¿qué hace libre?
Libre pondrá el bloque de memoria en su propia lista de bloqueo libre. 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 administrar 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 malloc ve cuando se necesita una nueva porción de memoria. Se escanea antes de solicitar una nueva memoria del sistema operativo. Cuando se encuentra un fragmento que es más grande que la memoria necesaria, se divide en dos partes. Uno se devuelve a la persona que llama, el otro se coloca de nuevo en la lista libre.
Hay muchas optimizaciones diferentes para este comportamiento estándar (por ejemplo, para pequeños trozos de memoria). Pero dado que malloc y free deben ser tan universales, el comportamiento estándar siempre es una alternativa cuando las alternativas no son utilizables. También hay optimizaciones en el manejo de la lista libre, por ejemplo, almacenar los trozos en listas ordenadas por tamaño. Pero todas las optimizaciones también tienen sus propias limitaciones.
¿Por qué se bloquea su código:
La razón es que al escribir 9 caracteres (no olvide el byte nulo final) en un área con un tamaño de 4 caracteres, es probable que sobrescriba los datos administrativos almacenados en otra parte de la memoria que se encuentra "detrás" de su parte de datos ( ya que estos datos se almacenan con mayor frecuencia "en frente" de los fragmentos de memoria). Cuando está libre, intenta colocar tu parte en la lista libre, puede tocar estos datos administrativos y, por lo tanto, tropezar con un puntero sobrescrito. Esto bloqueará el sistema.
Este es un comportamiento bastante agraciado. También he visto situaciones en las que un puntero fuera de control en algún lugar ha sobrescrito los datos en la lista sin memoria y el sistema no se bloqueó de inmediato, sino algunas subrutinas más tarde. Incluso en un sistema de complejidad media, tales problemas pueden ser realmente, realmente difíciles de depurar. En el caso en el que estuve involucrado, nos llevó (un grupo más grande de desarrolladores) varios días encontrar el motivo del fallo, ya que estaba en una ubicación totalmente diferente a la indicada por el volcado de memoria. Es como una bomba de tiempo. Ya sabes, tu próximo "libre" o "malloc" se estrellará, ¡pero no sabes por qué!
Esos son algunos de los peores problemas de C / C ++, y una de las razones por las que los punteros pueden ser tan problemáticos.
Su línea de strcpy intenta almacenar 9 bytes, no 8, debido al terminador NUL. Invoca un comportamiento indefinido.
La llamada a la libre puede o no puede bloquearse. La memoria "después" de los 4 bytes de su asignación puede ser utilizada para otra cosa por su implementación de C o C ++. Si se usa para otra cosa, garabatear por todas partes causará que "otra cosa" salga mal, pero si no se usa para otra cosa, entonces puede que salgas con la suya. "Salirse con la suya" puede sonar bien, pero en realidad es malo, ya que significa que su código parecerá funcionar bien, pero en un futuro no podría salirse con la suya.
Con un asignador de memoria de estilo de depuración, es posible que se haya escrito un valor de protección especial allí, y que las comprobaciones gratuitas para ese valor y el pánico si no lo encuentra.
De lo contrario, es posible que los siguientes 5 bytes incluyan parte de un nodo de enlace que pertenece a algún otro bloque de memoria que aún no se ha asignado. La liberación de su bloque podría implicar agregarlo a una lista de bloques disponibles, y debido a que ha garabateado en el nodo de la lista, esa operación podría eliminar la referencia de un puntero con un valor no válido, lo que provocaría un bloqueo.
Todo depende del asignador de memoria: las diferentes implementaciones utilizan diferentes mecanismos.
También es importante darse cuenta de que simplemente mover el puntero de interrupción del programa con brk
y sbrk
no asigna realmente la memoria, solo configura el espacio de direcciones. En Linux, por ejemplo, la memoria estará "respaldada" por las páginas físicas reales cuando se acceda a ese rango de direcciones, lo que provocará un fallo en la página, y eventualmente provocará que el kernel haga una llamada al asignador de páginas para obtener una página de respaldo.
Tu programa se bloquea porque usa memoria que no te pertenece. Puede ser utilizado por otra persona o no: si tiene suerte, se bloquea, si no, el problema puede permanecer oculto durante mucho tiempo y volver más tarde.
En cuanto a la implementación de malloc / free, libros enteros están dedicados al tema. Básicamente, el asignador obtendría grandes porciones de memoria del sistema operativo y las administraría por usted. Algunos de los problemas que un asignador debe abordar son:
- Cómo obtener nueva memoria
- Cómo almacenarlo - (lista u otra estructura, listas múltiples para trozos de memoria de diferente tamaño, etc.)
- Qué hacer si el usuario solicita más memoria que la disponible actualmente (solicite más memoria del sistema operativo, únase a algunos de los bloques existentes, cómo unirlos exactamente, ...)
- Qué hacer cuando el usuario libera memoria.
- Los asignadores de depuración pueden darle una porción más grande que usted solicitó y rellenarla con algún patrón de bytes, cuando libera la memoria, el asignador puede verificar si se escribió fuera del bloque (lo que probablemente suceda en su caso) ...
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 se encuentran el bloque siguiente y el anterior.
- Cuando entra una llamada a malloc, se hace referencia a una lista que apunta a bloques del tamaño apropiado.
- Este bloque se devuelve y los encabezados y pies de página se actualizan en consecuencia.