Bueno, sé que hay cosas como malloc / free para C y new / using-a-destructor para la administración de memoria en C ++, pero me preguntaba por qué no hay "nuevas actualizaciones" para estos lenguajes que permitan al usuario ¿tiene la opción de administrar la memoria manualmente o que el sistema lo haga automáticamente (recolección de basura)?
Una pregunta algo novedosa, pero solo he estado en CS durante aproximadamente un año.
garbage-collection
Templario oscuro
fuente
fuente
Respuestas:
La recolección de basura requiere estructuras de datos para rastrear las asignaciones y / o el recuento de referencias. Estos crean gastos generales en la memoria, el rendimiento y la complejidad del lenguaje. C ++ está diseñado para estar "cerca del metal", en otras palabras, toma el lado de mayor rendimiento de las características de compensación frente a conveniencia. Otros idiomas hacen que esa compensación sea diferente. Esta es una de las consideraciones al elegir un idioma, qué énfasis prefiere.
Dicho esto, hay muchos esquemas para el recuento de referencias en C ++ que son bastante livianos y de alto rendimiento, pero están en bibliotecas, tanto comerciales como de código abierto, en lugar de ser parte del lenguaje en sí. El recuento de referencias para administrar la vida útil de los objetos no es lo mismo que la recolección de basura, pero aborda muchos de los mismos tipos de problemas y se ajusta mejor con el enfoque básico de C ++.
fuente
Estrictamente hablando, no hay administración de memoria en absoluto en el lenguaje C. malloc () y free () no son palabras clave en el lenguaje, sino funciones que se llaman desde una biblioteca. Esta distinción puede ser pedante ahora, porque malloc () y free () son parte de la biblioteca estándar de C, y será proporcionada por cualquier implementación de C que cumpla con los estándares, pero esto no siempre fue así en el pasado.
¿Por qué querrías un idioma sin estándar para la administración de memoria? Esto se remonta a los orígenes de C como 'ensamblaje portátil'. Existen muchos casos de hardware y algoritmos que pueden beneficiarse o incluso requerir técnicas especializadas de administración de memoria. Hasta donde sé, no hay forma de deshabilitar por completo la administración de memoria nativa de Java y reemplazarla por la suya. Esto simplemente no es aceptable en algunas situaciones de alto rendimiento / recursos mínimos. C proporciona una flexibilidad casi completa para elegir exactamente qué infraestructura utilizará su programa. El precio pagado es que el lenguaje C proporciona muy poca ayuda para escribir un código correcto y sin errores.
fuente
malloc()
ofree()
. (por ejemplo, compiladores MLAP para PIC)La verdadera respuesta es que la única forma de crear un mecanismo de recolección de basura seguro y eficiente es tener soporte a nivel de lenguaje para referencias opacas. (O, por el contrario, la falta de soporte a nivel de lenguaje para la manipulación directa de la memoria).
Java y C # pueden hacerlo porque tienen tipos de referencia especiales que no pueden ser manipulados. Esto le da al tiempo de ejecución la libertad de hacer cosas como mover objetos asignados en la memoria , que es crucial para una implementación de GC de alto rendimiento.
Para el registro, ninguna implementación moderna de GC utiliza el recuento de referencias , por lo que es completamente una pista falsa. Los GC modernos utilizan la colección generacional, donde las nuevas asignaciones se tratan esencialmente de la misma manera que las asignaciones de pila están en un lenguaje como C ++, y luego periódicamente los objetos recién asignados que aún están vivos se mueven a un espacio "sobreviviente" separado, y una generación entera de objetos se desasigna a la vez.
Este enfoque tiene ventajas y desventajas: la ventaja es que las asignaciones de montón en un lenguaje que admite GC son tan rápidas como las asignaciones de pila en un lenguaje que no admite GC, y la desventaja es que los objetos que necesitan realizar una limpieza antes de ser destruidos requieren un mecanismo separado (por ejemplo, la
using
palabra clave de C # ) o de lo contrario su código de limpieza se ejecuta de forma no determinista.Tenga en cuenta que una clave para un GC de alto rendimiento es que debe haber soporte de idioma para una clase especial de referencias. C no tiene soporte para este lenguaje y nunca lo tendrá; debido a que C ++ tiene una sobrecarga del operador, podría emular un tipo de puntero GC'd, aunque tendría que hacerse con cuidado. De hecho, cuando Microsoft inventó su dialecto de C ++ que se ejecutaría bajo CLR (el tiempo de ejecución de .NET), tuvieron que inventar una nueva sintaxis para "referencias de estilo C #" (por ejemplo
Foo^
) para distinguirlas de "referencias de estilo C ++" (por ejemploFoo&
)Lo que sí tiene C ++, y lo que usan regularmente los programadores de C ++, son punteros inteligentes , que en realidad son solo un mecanismo de conteo de referencias. No consideraría el conteo de referencias como GC "verdadero", pero proporciona muchos de los mismos beneficios, a costa de un rendimiento más lento que la gestión de memoria manual o el GC verdadero, pero con la ventaja de la destrucción determinista.
Al final del día, la respuesta realmente se reduce a una función de diseño del lenguaje. C hizo una elección, C ++ hizo una elección que le permitía ser compatible con versiones anteriores de C y al mismo tiempo proporcionar alternativas que son lo suficientemente buenas para la mayoría de los propósitos, y Java y C # hicieron una elección diferente que es incompatible con C pero que también es lo suficientemente buena para La mayoría de los propósitos. Desafortunadamente, no hay una bala de plata, pero estar familiarizado con las diferentes opciones disponibles le ayudará a elegir la correcta para cualquier programa que esté tratando de construir actualmente.
fuente
std::unique_ptr
es "soporte a nivel de lenguaje para referencias opacas"? (No era el tipo de soporte al que me refería, y tampoco creo que sea suficiente a menos que el soporte para la manipulación directa de la memoria también se elimine de C ++.) Menciono los punteros inteligentes en mi respuesta, y consideraríastd:unique_ptr
un puntero inteligente , dado que realmente hace un recuento de referencias, solo admite los casos especiales en los que el número de referencias es cero o uno (ystd::move
es el mecanismo de actualización del recuento de referencias).std::unique_ptr
no tiene un recuento de referencias ystd::move
no tiene nada que ver con las referencias (por lo tanto, "no" golpeó el rendimiento). Sin embargo, veo su punto, yastd::shared_ptr
que tiene un recuento de referencias que es implícitamente actualizado porstd::move
:)malloc
yfree
. Entonces, sí, un GC puede ser sustancialmente más rápido. (Tenga en cuenta que dije "puede ser", por supuesto, el rendimiento exacto de cada programa se ve afectado por muchos factores).Porque, cuando se usa el poder de C ++, no hay necesidad.
Herb Sutter: " No he escrito eliminar en años " .
ver Escribir código C ++ moderno: cómo C ++ ha evolucionado a lo largo de los años 21:10
Puede sorprender a muchos programadores experimentados de C ++.
fuente
"Todo" un recolector de basura es un proceso que se ejecuta periódicamente para verificar si hay objetos no referenciados en la memoria y si los elimina. (Sí, sé que esto es una simplificación excesiva). Esto no es una propiedad del lenguaje, sino del marco.
Hay recolectores de basura escritos para C y C ++, este por ejemplo.
Una razón por la que no se ha "agregado" al lenguaje podría deberse al gran volumen de código existente que nunca lo usaría, ya que usan su propio código para administrar la memoria. Otra razón podría ser que los tipos de aplicaciones escritas en C y C ++ no necesitan la sobrecarga asociada con un proceso de recolección de basura.
fuente
malloc
yfree
, romperías mi programa correcto.free
hasta que hubiera terminado. Pero su recolector de basura propuesto que no libera la memoria hasta que llamo explícitamentefree
no es un recolector de basura en absoluto.C fue diseñado en una época en que la recolección de basura apenas era una opción. También estaba destinado a usos en los que la recolección de basura generalmente no funcionaría: entornos de metal desnudo en tiempo real con memoria mínima y soporte de tiempo de ejecución mínimo. Recuerde que C fue el lenguaje de implementación para el primer Unix, que se ejecutó en un pdp-11 con 64 * K * bytes de memoria. C ++ era originalmente una extensión de C: la elección ya se había hecho, y es muy difícil injertar la recolección de basura en un lenguaje existente. Es el tipo de cosa que debe construirse desde la planta baja.
fuente
No tengo las citas exactas, pero tanto Bjarne como Herb Sutter dicen algo similar:
En C ++ moderno, utiliza punteros inteligentes y, por lo tanto, no tiene basura.
fuente
Pregunta por qué estos idiomas no se han actualizado para incluir un recolector de basura opcional.
El problema con la recolección de basura opcional es que no puede mezclar código que usa los diferentes modelos. Es decir, si escribo un código que asume que está utilizando un recolector de basura, no puede usarlo en su programa que tiene desactivada la recolección de basura. Si lo hace, se filtrará por todas partes.
fuente
¿Te imaginas escribir un controlador de dispositivo en un idioma con recolección de basura? ¿Cuántos bits podrían aparecer en la línea mientras el GC estaba funcionando?
O un sistema operativo? ¿Cómo podría comenzar a ejecutar la recolección de basura antes incluso de iniciar el núcleo?
C está diseñado para bajo nivel cerca de las tareas de hardware. ¿El problema? es un lenguaje tan agradable que también es una buena opción para muchas tareas de nivel superior. Los zares del lenguaje son conscientes de estos usos, pero deben admitir los requisitos de los controladores de dispositivos, el código incrustado y los sistemas operativos como prioridad.
fuente
La respuesta breve y aburrida a esta pregunta es que es necesario que exista un lenguaje que no recolecte basura para las personas que escriben los recolectores de basura. Conceptualmente no es fácil tener un lenguaje que al mismo tiempo permita un control muy preciso sobre el diseño de la memoria y tenga un GC ejecutándose en la parte superior.
La otra pregunta es por qué C y C ++ no tienen recolectores de basura. Bueno, sé que C ++ tiene un par de ellos, pero no son muy populares porque se ven obligados a lidiar con un lenguaje que no fue diseñado para ser GC-ed en primer lugar, y las personas que todavía usan C ++ en esta edad no es realmente del tipo que pierde un GC.
Además, en lugar de agregar GC a un antiguo lenguaje no editado por GC, en realidad es más fácil crear un nuevo lenguaje que tenga la mayor parte de la misma sintaxis mientras admite un GC. Java y C # son buenos ejemplos de esto.
fuente
Hay varios problemas, incluyendo ...
delete
ofree
explícitamente. El enfoque de GC todavía tiene una ventaja, sin referencias colgantes, y el análisis estático puede detectar algunos casos, pero nuevamente, no hay una solución perfecta para todos los casos.Básicamente, en parte se trata de la edad de los idiomas, pero de todos modos siempre habrá un lugar para los idiomas que no son GC, incluso si es un lugar un tanto nicho. Y en serio, en C ++, la falta de GC no es un gran problema: su memoria se administra de manera diferente, pero no está sin administrar.
Microsofts administrado C ++ tiene al menos cierta capacidad de mezclar GC y no GC en la misma aplicación, lo que permite una combinación de las ventajas de cada uno, pero no tengo la experiencia para decir qué tan bien funciona en la práctica.
Enlaces de representación de putas a respuestas mías relacionadas ...
fuente
La recolección de basura es fundamentalmente incompatible con un lenguaje de sistemas utilizado para desarrollar controladores para hardware compatible con DMA.
Es completamente posible que el único puntero a un objeto se almacene en un registro de hardware en algún periférico. Dado que el recolector de basura no sabría sobre esto, pensaría que el objeto era inalcanzable y lo recogería.
Este argumento es doble para la compactación de GC. Incluso si tuviera cuidado de mantener referencias en memoria a los objetos utilizados por los periféricos de hardware, cuando el GC reubicara el objeto, no sabría cómo actualizar el puntero contenido en el registro de configuración de periféricos.
Entonces, ahora necesitaría una mezcla de memorias intermedias DMA inmóviles y objetos administrados por GC, lo que significa que tiene todas las desventajas de ambos.
fuente
Porque, C y C ++ son lenguajes de nivel relativamente bajo destinados a fines generales, incluso, por ejemplo, para ejecutarse en un procesador de 16 bits con 1 MB de memoria en un sistema integrado, que no podía permitirse el desperdicio de memoria con gc.
fuente
Hay recolectores de basura en C ++ y C. No estoy seguro de cómo funciona esto en C, pero en C ++ puede aprovechar RTTI para descubrir dinámicamente su gráfico de objetos y usarlo para la recolección de basura.
Que yo sepa, no puede escribir Java sin un recolector de basura. Una pequeña búsqueda resultó esto .
La diferencia clave entre Java y C / C ++ es que en C / C ++ la elección siempre es suya, mientras que en Java a menudo no tiene opciones por diseño.
fuente
Es una compensación entre rendimiento y seguridad.
No hay garantía de que su basura se recolecte en Java, por lo que puede quedarse usando espacio durante mucho tiempo, mientras que el escaneo de objetos sin referencia (es decir, basura) también lleva más tiempo que eliminar o liberar explícitamente un objeto no utilizado.
La ventaja es, por supuesto, que uno puede construir un lenguaje sin punteros o sin pérdidas de memoria, por lo que es más probable que produzca el código correcto.
A veces puede haber una ligera ventaja "religiosa" en estos debates: ¡ten cuidado!
fuente
Aquí hay una lista de problemas inherentes de GC, que lo hacen inutilizable en un lenguaje de sistema como C:
El GC tiene que ejecutarse por debajo del nivel del código cuyos objetos gestiona. Simplemente no existe tal nivel en un núcleo.
Un GC tiene que detener el código administrado de vez en cuando. Ahora piense en lo que sucedería si le hiciera eso a su núcleo. Todo el procesamiento en su máquina se detendría, por ejemplo, un milisegundo, mientras que el GC escanea todas las asignaciones de memoria existentes. Esto acabaría con todos los intentos de crear sistemas que funcionen bajo estrictos requisitos en tiempo real.
Un GC debe ser capaz de distinguir entre punteros y no punteros. Es decir, debe ser capaz de mirar cada objeto de memoria existente y ser capaz de producir una lista de desplazamientos donde se puedan encontrar sus punteros.
Este descubrimiento debe ser perfecto: el GC debe ser capaz de perseguir todos los punteros que descubre. Si desreferenciara un falso positivo, probablemente se estrellaría. Si no logra descubrir un falso negativo, probablemente destruiría un objeto que todavía está en uso, bloqueando el código administrado o corrompiendo silenciosamente sus datos.
Esto requiere absolutamente que la información de tipo se almacene en cada objeto existente. Sin embargo, tanto C como C ++ permiten objetos de datos antiguos simples que no contienen información de tipo.
GC es un negocio inherentemente lento. Los programadores que se han socializado con Java pueden no darse cuenta de esto, pero los programas pueden ser mucho más rápidos cuando no se implementan en Java. Y uno de los factores que hace que Java sea lento es GC. Esto es lo que impide que los lenguajes GCed como Java se utilicen en la supercomputación. Si su máquina cuesta un millón al año en consumo de energía, no desea pagar ni siquiera el 10% de eso por la recolección de basura.
C y C ++ son lenguajes que se crean para admitir todos los casos de uso posibles. Y, como puede ver, muchos de estos casos de uso están excluidos de la recolección de basura. Entonces, para admitir estos casos de uso, C / C ++ no se puede recolectar basura.
fuente