La memoria (y los bloqueos de recursos) se devuelven al sistema operativo en puntos deterministas durante la ejecución de un programa. El flujo de control de un programa por sí solo es suficiente para saber dónde, con seguridad, se puede desasignar un recurso dado. Al igual que un programador humano sabe dónde escribir fclose(file)
cuando el programa termina con él.
Los GC resuelven esto resolviéndolo directamente durante el tiempo de ejecución cuando se ejecuta el flujo de control. Pero la verdadera fuente de verdad sobre el flujo de control es la fuente. Entonces, en teoría, debería ser posible determinar dónde insertar las free()
llamadas antes de la compilación analizando la fuente (o AST).
El conteo de referencias es una forma obvia de implementar esto, pero es fácil encontrar situaciones en las que todavía se hace referencia a los punteros (todavía en el alcance) pero ya no son necesarios. Esto solo convierte la responsabilidad de desasignar manualmente punteros en una responsabilidad de administrar manualmente el alcance / referencias a esos punteros.
Parece que es posible escribir un programa que pueda leer la fuente de un programa y:
- predecir todas las permutaciones del flujo de control del programa --- con una precisión similar a la de ver la ejecución en vivo del programa
- rastrear todas las referencias a los recursos asignados
- para cada referencia, recorra todo el flujo de control posterior para encontrar el primer punto en el que se garantiza que la referencia nunca se desreferenciará
- en ese punto, inserte una declaración de desasignación en esa línea de código fuente
¿Hay algo por ahí que ya haga esto? No creo que los punteros inteligentes Rust o C ++ / RAII sean lo mismo.
Respuestas:
Tome este ejemplo (artificial):
¿Cuándo debería llamarse gratis? antes de malloc y asignar a
resource1
no podemos porque podría copiarseresource2
, antes de asignar aresource2
no podemos porque podríamos haber recibido 2 del usuario dos veces sin una intervención 1.La única forma de asegurarse es probar resource1 y resource2 para ver si no son iguales en los casos 1 y 2 y liberar el valor anterior si no lo fueran. Esto es esencialmente un recuento de referencias donde sabe que solo hay 2 referencias posibles.
fuente
RAII no es automáticamente lo mismo, pero tiene el mismo efecto. Proporciona una respuesta fácil a la pregunta "¿cómo sabes cuándo ya no se puede acceder a esto?" mediante el uso de alcance para cubrir el área cuando se utiliza un recurso en particular.
Es posible que desee considerar el problema similar "¿cómo puedo saber que mi programa no sufrirá un error de tipo en tiempo de ejecución?". La solución a esto no es predecir todas las rutas de ejecución a través del programa, sino mediante el uso de un sistema de anotación de tipo e inferencia para demostrar que no puede haber tal error. Rust es un intento de extender esta propiedad de prueba a la asignación de memoria.
Es posible escribir pruebas sobre el comportamiento del programa sin tener que resolver el problema de detención, pero solo si usa anotaciones de algún tipo para restringir el programa. Ver también pruebas de seguridad (sel4 etc.)
fuente
Sí, esto existe en la naturaleza. ML Kit es un compilador de calidad de producción que tiene la estrategia descrita (más o menos) como una de sus opciones de administración de memoria disponibles. También permite el uso de un GC convencional o hibridación con recuento de referencias (puede usar un generador de perfiles de montón para ver qué estrategia realmente producirá los mejores resultados para su programa).
Una retrospectiva sobre la gestión de memoria basada en la región es un artículo de los autores originales del ML Kit que explica sus éxitos y fracasos. La conclusión final es que la estrategia es práctica cuando se escribe con la ayuda de un perfilador de montón.
(Esta es una buena ilustración de por qué generalmente no debería buscar en el Problema de detención una respuesta a preguntas prácticas de ingeniería: no queremos o necesitamos resolver el caso general de la mayoría de los programas realistas).
fuente
Aquí es donde radica el problema. La cantidad de permutaciones es tan grande (en la práctica es infinita) para cualquier programa no trivial, que el tiempo y la memoria necesarios lo harían completamente impracticable.
fuente
El problema de detención demuestra que esto no es posible en todos los casos. Sin embargo, todavía es posible en muchos casos, y de hecho, lo hacen casi todos los compiladores para probablemente la mayoría de las variables. Así es como un compilador puede decir que es seguro simplemente asignar una variable en la pila o incluso un registro, en lugar de un almacenamiento dinámico a largo plazo.
Si tiene funciones puras o una semántica de propiedad realmente buena, puede ampliar ese análisis estático aún más, aunque se vuelve prohibitivamente más costoso hacerlo mientras más ramas tome su código.
fuente
Si un solo programador o equipo escribe todo el programa, es razonable que se puedan identificar los puntos de diseño donde se debe liberar la memoria (y otros recursos). Por lo tanto, sí, el análisis estático del diseño puede ser suficiente en contextos más limitados.
Sin embargo, cuando tiene en cuenta las DLL, API, marcos de trabajo de terceros (y también agrega hilos), puede ser muy difícil (es decir, imposible en todos los casos) para los programadores que usan razonar correctamente sobre qué entidad posee qué memoria y cuando el último uso es. Nuestro sospechoso habitual de idiomas no documenta suficientemente la transferencia de propiedad de memoria de objetos y matrices, superficial y profunda. Si un programador no puede razonar sobre eso (¡estática o dinámicamente!), Entonces un compilador probablemente tampoco. Nuevamente, esto se debe al hecho de que las transferencias de propiedad de memoria no se capturan en llamadas a métodos o por interfaces, etc., por lo tanto, no, no es posible predecir estáticamente cuándo o dónde en el código liberar memoria.
Como este es un problema tan grave, muchos idiomas modernos eligen la recolección de basura, que recupera automáticamente la memoria en algún momento después de la última referencia en vivo. Sin embargo, GC tiene un costo de rendimiento significativo (especialmente para aplicaciones en tiempo real), por lo que no es una cura universal para todos. Además, aún puede tener pérdidas de memoria con GC (por ejemplo, una colección que solo crece). Aún así, esta es una buena solución para la mayoría de los ejercicios de programación.
Hay algunas alternativas (algunas emergentes).
El lenguaje Rust lleva a RAII a un extremo. Proporciona construcciones lingüísticas que definen la transferencia de propiedad en métodos de clases e interfaces con más detalle, por ejemplo, objetos transferidos a prestados entre una persona que llama y una persona que llama, o en objetos de mayor duración. Proporciona un alto nivel de seguridad en el tiempo de compilación para la gestión de la memoria. Sin embargo, no es un lenguaje trivial para aprender, y tampoco está exento de problemas (por ejemplo, no creo que el diseño sea completamente estable, ciertas cosas todavía se están experimentando y, por lo tanto, están cambiando).
Swift y Objective-C siguen otra ruta, que es el conteo de referencias en su mayoría automáticas. El recuento de referencias entra en problemas con los ciclos y existen desafíos importantes para los programadores, por ejemplo, especialmente con los cierres.
fuente
Si un programa no depende de ninguna entrada desconocida, entonces sí, debería ser posible (con la advertencia de que puede ser una tarea compleja y puede llevar mucho tiempo; pero eso también sería cierto para el programa). Dichos programas serían completamente solucionables en tiempo de compilación; en términos de C ++, podrían estar (casi) completamente compuestos de
constexpr
s. Ejemplos simples serían calcular los primeros 100 dígitos de pi o clasificar un diccionario conocido.fuente
La liberación de memoria, en general, es equivalente al problema de detención: si no puede determinar estáticamente si un programa se detendrá (estáticamente), tampoco puede determinar si liberará memoria (estáticamente).
https://en.wikipedia.org/wiki/Halting_problem
Dicho esto, Rust es muy agradable ... https://doc.rust-lang.org/book/ownership.html
fuente