Cuando sale de una aplicación C, ¿se libera automáticamente la memoria malloc-ed?
Digamos que tengo el siguiente código C:
int main () {
int *p = malloc(10 * sizeof *p);
*p = 42;
return 0; //Exiting without freeing the allocated memory
}
Cuando compilo y ejecuto ese programa en C, es decir, después de asignar algo de espacio en la memoria, ¿la memoria que asigné seguirá asignada (es decir, básicamente ocupará espacio) después de que salga de la aplicación y finalice el proceso?
Depende del sistema operativo. La mayoría de los sistemas operativos modernos (y todos los principales) liberarán la memoria que el programa no libera cuando finaliza.
Depender de esto es una mala práctica y es mejor liberarlo explícitamente. El problema no es sólo que su código se vea mal. Quizás decida que desea integrar su pequeño programa en uno más grande y de larga duración. Luego, un tiempo después, tienes que pasar horas rastreando pérdidas de memoria.
Depender de una característica de un sistema operativo también hace que el código sea menos portátil.
En general, los sistemas operativos modernos de propósito general realizan limpieza después de procesos finalizados . Esto es necesario porque la alternativa es que el sistema pierda recursos con el tiempo y requiera reiniciarse debido a programas que están mal escritos o simplemente tienen errores que rara vez ocurren y que pierden recursos.
Hacer que su programa libere explícitamente sus recursos de todos modos puede ser una buena práctica por varias razones, como por ejemplo:
- Si tiene recursos adicionales que el sistema operativo no limpia al salir, como archivos temporales o cualquier tipo de cambio en el estado de un recurso externo, entonces necesitará código para manejar todas esas cosas al salir, y esto A menudo se combina elegantemente con la liberación de la memoria.
- Si su programa comienza a tener una vida útil más larga, entonces no querrá que la única forma de liberar memoria sea salir. Por ejemplo, es posible que desee convertir su programa en un servidor (daemon) que siga ejecutándose mientras maneja muchas solicitudes de unidades de trabajo individuales, o su programa podría convertirse en una pequeña parte de un programa más grande.
Sin embargo, aquí hay una razón para omitir la liberación de memoria: apagado eficiente . Por ejemplo, supongamos que su aplicación contiene un caché grande en la memoria. Si cuando sale recorre toda la estructura de caché y la libera una pieza a la vez, eso no sirve para nada y desperdicia recursos. Especialmente, considere el caso en el que el sistema operativo ha intercambiado las páginas de memoria que contienen su caché al disco; al recorrer la estructura y liberarla, estás trayendo todas esas páginas nuevamente a la memoria al mismo tiempo , desperdiciando mucho tiempo y energía sin ningún beneficio real, ¡y posiblemente incluso provocando que otros programas en el sistema sean intercambiados!
Como ejemplo relacionado, hay servidores de alto rendimiento que funcionan creando un proceso para cada solicitud y luego saliendo cuando finaliza; de esta manera ni siquiera tienen que realizar un seguimiento de la asignación de memoria y nunca realizan ninguna liberación o recolección de basura, ya que todo simplemente desaparece en la memoria libre del sistema operativo al final del proceso. (Se puede hacer el mismo tipo de cosas dentro de un proceso usando un asignador de memoria personalizado, pero requiere una programación muy cuidadosa; esencialmente, crear una propia noción de "procesos livianos" dentro del proceso del sistema operativo).
Mis disculpas por publicar tanto tiempo después de la última publicación en este hilo.
Un punto adicional. No todos los programas logran salir airosos. Los fallos y Ctrl-C, etc. harán que un programa se cierre de forma incontrolada. Si su sistema operativo no libera su montón, no limpia su pila, elimina variables estáticas, etc., eventualmente bloqueará su sistema debido a pérdidas de memoria o algo peor.
Aparte de esto, es interesante que Ubuntu se bloquee o se rompa, y sospecho que todos los demás sistemas operativos modernos tienen problemas con los recursos "manejados". Los sockets, archivos, dispositivos, etc. pueden permanecer "abiertos" cuando un programa finaliza o falla. También es una buena práctica cerrar cualquier cosa con un "identificador" o "descriptor" como parte de su limpieza antes de una salida elegante.
Actualmente estoy desarrollando un programa que utiliza mucho sockets. Cuando me quedo atascado en un bloqueo, tengo que presionar Ctrl-c para salir de él, por lo que mis enchufes se quedan varados. Agregué un std::vector para recopilar una lista de todos los sockets abiertos y un controlador de sigaction que detecta sigint y sigterm. El controlador recorre la lista y cierra los sockets. Planeo hacer una rutina de limpieza similar para usar antes de los lanzamientos que conducirán a una terminación prematura.
¿Alguien quiere comentar sobre este diseño?
Lo que sucede aquí ( en un sistema operativo moderno ) es que su programa se ejecuta dentro de su propio "proceso". Esta es una entidad del sistema operativo que está dotada de su propio espacio de direcciones, descriptores de archivos, etc. Sus malloc
llamadas asignan memoria del "montón" o páginas de memoria no asignadas que están asignadas a su proceso.
Cuando su programa finaliza, como en este ejemplo, el sistema operativo simplemente recicla/derriba todos los recursos asignados a su proceso. En el caso de la memoria, todas las páginas de memoria que se le asignan simplemente se marcan como "libres" y se reciclan para su uso en otros procesos. Las páginas son un concepto de nivel inferior al que maneja malloc; como resultado, los detalles específicos de malloc/free simplemente se eliminan a medida que se limpia todo.
Es el equivalente moral a que, cuando terminas de usar tu computadora portátil y quieres dársela a un amigo, no te molestas en eliminar cada archivo individualmente. Simplemente formatea el disco duro.
Dicho todo esto, como señalan todos los demás respondedores, confiar en esto no es una buena práctica:
- Siempre debes programar para cuidar los recursos, y en C eso también significa memoria. Podría terminar incrustando su código en una biblioteca o podría terminar ejecutándose mucho más de lo esperado.
- Es posible que algunos sistemas operativos (los más antiguos y tal vez algunos integrados modernos) no mantengan límites de proceso tan estrictos, y sus asignaciones pueden afectar los espacios de direcciones de otros.