Java tiene un GC automático que de vez en cuando detiene el mundo, pero se ocupa de la basura en un montón. Ahora las aplicaciones C / C ++ no tienen estos bloqueos STW, su uso de memoria tampoco crece infinitamente. ¿Cómo se logra este comportamiento? ¿Cómo se cuidan los objetos muertos?
c++
c
garbage-collection
Ju Shua
fuente
fuente
new
.What happens to garbage in C++?
¿No se suele compilar en un ejecutable?Respuestas:
El programador es responsable de garantizar que los objetos que crearon
new
se eliminen mediantedelete
. Si se crea un objeto, pero no se destruye antes de que el último puntero o referencia se salga de su alcance, se cae por las grietas y se convierte en una Fuga de memoria .Desafortunadamente para C, C ++ y otros lenguajes que no incluyen un GC, esto simplemente se acumula con el tiempo. Puede provocar que una aplicación o el sistema se quede sin memoria y no pueda asignar nuevos bloques de memoria. En este punto, el usuario debe recurrir a finalizar la aplicación para que el sistema operativo pueda recuperar esa memoria utilizada.
En cuanto a mitigar este problema, hay varias cosas que hacen la vida de un programador mucho más fácil. Estos son apoyados principalmente por la naturaleza del alcance .
Aquí, creamos dos variables. Existen en Block Scope , según lo definido por las
{}
llaves. Cuando la ejecución se sale de este ámbito, estos objetos se eliminarán automáticamente. En este caso,variableThatIsAPointer
como su nombre lo indica, es un puntero a un objeto en la memoria. Cuando se sale del alcance, el puntero se elimina, pero el objeto al que apunta permanece. Aquí, hacemosdelete
este objeto antes de que salga del alcance para garantizar que no haya pérdida de memoria. Sin embargo, también podríamos haber pasado este puntero a otra parte y esperar que se elimine más adelante.Esta naturaleza del alcance se extiende a las clases:
Aquí, se aplica el mismo principio. No tenemos que preocuparnos por
bar
cuándoFoo
se elimina. Sin embargootherBar
, solo se elimina el puntero. SiotherBar
es el único puntero válido a cualquier objeto al que apunte, probablemente deberíamosdelete
hacerlo enFoo
el destructor. Este es el concepto de conducción detrás de RAIIRAII es también la fuerza impulsora típica detrás de los Smart Pointers . En la biblioteca estándar de C ++, estos son
std::shared_ptr
,std::unique_ptr
ystd::weak_ptr
; aunque he visto y utilizado otrosshared_ptr
/weak_ptr
implementaciones que siguen los mismos conceptos. Para estos, un contador de referencia rastrea la cantidad de punteros que hay para un objeto dado, y automáticamentedelete
envía el objeto una vez que no hay más referencias a él.Más allá de eso, todo se reduce a prácticas adecuadas y disciplina para que un programador se asegure de que su código maneje los objetos correctamente.
fuente
delete
- eso es lo que estaba buscando. Increíble.delete
solicitan automáticamente si los usa, por lo que debe considerar usarlos cada vez que no se pueda usar un almacenamiento automático.delete
el código de su aplicación (y de C ++ 14 en adelante, lo mismo connew
), sino usar punteros inteligentes y RAII para eliminar los objetos del montón.std::unique_ptr
tipo ystd::make_unique
función son el reemplazo directo y más simple denew
ydelete
a nivel de código de aplicación.C ++ no tiene recolección de basura.
Se requieren aplicaciones C ++ para deshacerse de su propia basura.
Se requiere que los programadores de aplicaciones C ++ entiendan esto.
Cuando se olvidan, el resultado se denomina "pérdida de memoria".
fuente
new
ydelete
.malloc
yfree
onew[]
ydelete[]
, o cualquier otro asignadores (como Windows deGlobalAlloc
,LocalAlloc
,SHAlloc
,CoTaskMemAlloc
,VirtualAlloc
,HeapAlloc
, ...), y la memoria asignada para usted (por ejemplo, a travésfopen
).En C, C ++ y otros sistemas sin un recolector de basura, el lenguaje y sus bibliotecas ofrecen facilidades al desarrollador para indicar cuándo se puede recuperar la memoria.
La instalación más básica es el almacenamiento automático . Muchas veces, el lenguaje en sí mismo asegura que los elementos se eliminen:
En estos casos, el compilador se encarga de saber cuándo esos valores no se utilizan y reclamar el almacenamiento asociado a ellos.
Cuando se usa almacenamiento dinámico , en C, la memoria se asigna
malloc
y recupera tradicionalmente confree
. En C ++, la memoria se asignanew
y recupera tradicionalmente condelete
.C no ha cambiado mucho a lo largo de los años, sin embargo, C ++ moderno evita
new
ydelete
completamente y depende de las instalaciones de la biblioteca (que ellos mismos usannew
y de maneradelete
apropiada):std::unique_ptr
ystd::shared_ptr
std::string
,std::vector
,std::map
, ... gestionar toda internamente memoria asignada dinámicamente de forma transparenteHablando de
shared_ptr
eso, existe un riesgo: si se forma un ciclo de referencias, y no se rompe, entonces puede haber pérdida de memoria. Depende del desarrollador evitar esta situación, la forma más sencilla de evitarlos porshared_ptr
completo y la segunda más simple evitar los ciclos a nivel de tipo.Como resultado , las pérdidas de memoria no son un problema en C ++ , incluso para los nuevos usuarios, siempre que se abstengan de usar
new
,delete
ostd::shared_ptr
. Esto es diferente a C, donde es necesaria una disciplina firme, y generalmente insuficiente.Sin embargo, esta respuesta no estaría completa sin mencionar la hermana gemela de las pérdidas de memoria: punteros colgantes .
Un puntero colgante (o referencia colgante) es un peligro creado al mantener un puntero o referencia a un objeto que está muerto. Por ejemplo:
El uso de un puntero colgante o referencia es Comportamiento indefinido . En general, afortunadamente, este es un choque inmediato; con bastante frecuencia, desafortunadamente, esto causa daños en la memoria primero ... y de vez en cuando surge un comportamiento extraño porque el compilador emite un código realmente extraño.
El comportamiento indefinido es el mayor problema con C y C ++ hasta el día de hoy, en términos de seguridad / corrección de los programas. Es posible que desee consultar Rust para un idioma sin recolector de basura y sin comportamiento indefinido.
fuente
new
,delete
yshared_ptr
"; sinnew
yshared_ptr
usted tiene propiedad directa para que no haya fugas. Por supuesto, es probable que tengas punteros colgantes, etc., pero me temo que debes dejar C ++ para deshacerte de ellos.C ++ tiene esta cosa llamada RAII . Básicamente significa que la basura se limpia a medida que avanza en lugar de dejarla en un montón y dejar que el limpiador lo arregle después de usted. (Imagíneme en mi habitación viendo el fútbol; mientras bebo latas de cerveza y necesito otras nuevas, la forma C ++ es llevar la lata vacía a la papelera de camino al refrigerador, la forma C # es tirarla al piso y espera a que la criada los recoja cuando venga a hacer la limpieza).
Ahora es posible perder memoria en C ++, pero para hacerlo es necesario dejar las construcciones habituales y volver a la forma C de hacer las cosas: asignar un bloque de memoria y realizar un seguimiento de dónde está ese bloque sin ninguna ayuda de lenguaje. Algunas personas olvidan este puntero y, por lo tanto, no pueden eliminar el bloque.
fuente
Cabe señalar que es, en el caso de C ++, una idea errónea común de que "es necesario hacer una gestión manual de la memoria". De hecho, generalmente no realiza ninguna gestión de memoria en su código.
Objetos de tamaño fijo (con alcance de por vida)
En la gran mayoría de los casos cuando necesita un objeto, el objeto tendrá una vida útil definida en su programa y se creará en la pila. Esto funciona para todos los tipos de datos primitivos integrados, pero también para instancias de clases y estructuras:
Los objetos de pila se eliminan automáticamente cuando finaliza la función. En Java, los objetos siempre se crean en el montón y, por lo tanto, deben eliminarse mediante algún mecanismo, como la recolección de basura. Esto no es un problema para los objetos de pila.
Objetos que administran datos dinámicos (con alcance de por vida)
Usar espacio en la pila funciona para objetos de un tamaño fijo. Cuando necesita una cantidad variable de espacio, como una matriz, se utiliza otro enfoque: la lista se encapsula en un objeto de tamaño fijo que administra la memoria dinámica por usted. Esto funciona porque los objetos pueden tener una función de limpieza especial, el destructor. Se garantiza que se llamará cuando el objeto esté fuera de alcance y haga lo contrario del constructor:
No hay administración de memoria en absoluto en el código donde se usa la memoria. Lo único que debemos asegurarnos es que el objeto que escribimos tiene un destructor adecuado. No importa cómo dejemos el alcance de
listTest
, ya sea a través de una excepción o simplemente al regresar de él,~MyList()
se llamará al destructor y no necesitamos administrar ninguna memoria.(Creo que es una decisión divertida de diseño usar el operador NOT binario
~
para indicar el destructor. Cuando se usa en números, invierte los bits; en analogía, aquí indica que lo que hizo el constructor está invertido).Básicamente, todos los objetos C ++ que necesitan memoria dinámica usan esta encapsulación. Se le ha llamado RAII ("adquisición de recursos es inicialización"), que es una forma bastante extraña de expresar la simple idea de que los objetos se preocupan por sus propios contenidos; lo que adquieren es suyo para limpiarlo.
Objetos polimórficos y vida útil más allá del alcance
Ahora, ambos casos fueron para memoria que tiene una vida útil claramente definida: la vida útil es la misma que el alcance. Si no queremos que un objeto caduque cuando dejamos el alcance, existe un tercer mecanismo que puede administrar la memoria para nosotros: un puntero inteligente. Los punteros inteligentes también se usan cuando tiene instancias de objetos cuyo tipo varía en tiempo de ejecución, pero que tienen una interfaz común o clase base:
Hay otro tipo de puntero inteligente
std::shared_ptr
para compartir objetos entre varios clientes. Solo eliminan su objeto contenido cuando el último cliente queda fuera de alcance, por lo que pueden usarse en situaciones en las que se desconoce por completo cuántos clientes habrá y durante cuánto tiempo usarán el objeto.En resumen, vemos que realmente no se realiza ninguna gestión manual de la memoria. Todo se encapsula y se resuelve mediante una gestión de memoria completamente automática y basada en el alcance. En los casos en que esto no es suficiente, se utilizan punteros inteligentes que encapsulan la memoria sin procesar.
Se considera una práctica extremadamente mala usar punteros sin formato como propietarios de recursos en cualquier parte del código C ++, asignaciones sin formato fuera de los constructores y
delete
llamadas sin formato fuera de los destructores, ya que son casi imposibles de administrar cuando se producen excepciones y, en general, difíciles de usar de manera segura.Lo mejor: esto funciona para todo tipo de recursos
Uno de los mayores beneficios de RAII es que no se limita a la memoria. En realidad, proporciona una forma muy natural de administrar recursos como archivos y sockets (apertura / cierre) y mecanismos de sincronización como mutexes (bloqueo / desbloqueo). Básicamente, cada recurso que puede adquirirse y liberarse se administra exactamente de la misma manera en C ++, y nada de esta administración se deja al usuario. Todo está encapsulado en clases que se adquieren en el constructor y se liberan en el destructor.
Por ejemplo, una función que bloquea un mutex generalmente se escribe así en C ++:
Otros idiomas hacen que esto sea mucho más complicado, ya que requieren que lo haga manualmente (por ejemplo, en una
finally
cláusula) o generan mecanismos especializados que resuelven este problema, pero no de una manera particularmente elegante (generalmente más adelante en su vida, cuando suficientes personas tienen sufrió de la deficiencia). Dichos mecanismos son de prueba con recursos en Java y la declaración de uso en C #, los cuales son aproximaciones de la RAII de C ++.Entonces, para resumir, todo esto fue una explicación muy superficial de RAII en C ++, pero espero que ayude a los lectores a comprender que la memoria e incluso la administración de recursos en C ++ generalmente no es "manual", sino que en realidad es principalmente automática.
fuente
delete
y estás muerto", las respuestas se disparan por encima de los 30 puntos y son aceptados, mientras que este tiene cinco. ¿Alguien realmente usa C ++ aquí?Con respecto a C específicamente, el lenguaje no le brinda herramientas para administrar la memoria asignada dinámicamente. Usted es absolutamente responsable de asegurarse de que cada uno
*alloc
tenga un correspondiente enfree
alguna parte.Donde las cosas se ponen realmente feas es cuando una asignación de recursos falla a mitad de camino; ¿lo intentas de nuevo, retrocedes y vuelves a empezar desde el principio, retrocedes y sales con un error, simplemente rescatas y dejas que el sistema operativo lo solucione?
Por ejemplo, aquí hay una función para asignar una matriz 2D no contigua. El comportamiento aquí es que si ocurre una falla de asignación a mitad del proceso, revertimos todo y devolvemos una indicación de error usando un puntero NULL:
Este código es feo con esos
goto
s, pero, en ausencia de cualquier tipo de mecanismo estructurado de manejo de excepciones, esta es prácticamente la única forma de lidiar con el problema sin tener que rescatarlo por completo, especialmente si su código de asignación de recursos está anidado más de un bucle de profundidad. Esta es una de las pocas veces en quegoto
es realmente una opción atractiva; de lo contrario, está utilizando un grupo de banderas yif
declaraciones adicionales .Puedes hacerte la vida más fácil escribiendo funciones dedicadas de asignación / reparto para cada recurso, algo así como
fuente
goto
declaraciones. Esta es una práctica recomendada en algunas áreas. Es un esquema de uso común para proteger contra el equivalente de excepciones en C. Eche un vistazo al código del kernel de Linux, que está repleto degoto
declaraciones y que no tiene fugas.goto
es extraño. Sería más legible si cambiarasgoto done;
areturn arr;
yarr=NULL;done:return arr;
parareturn NULL;
. Aunque en casos más complicados puede haber múltiplesgoto
s, comenzando a desenrollarse en diferentes niveles de preparación (lo que se haría al desenrollar la pila de excepciones en C ++).Aprendí a clasificar los problemas de memoria en varias categorías diferentes.
Una vez gotea. Supongamos que un programa pierde 100 bytes en el momento del inicio, solo para no volver a tener pérdidas. Perseguir y eliminar esas fugas únicas es bueno (me gusta tener un informe limpio por una capacidad de detección de fugas) pero no es esencial. A veces hay problemas más grandes que deben ser atacados.
Repetidas fugas. Una función que se llama de forma repetitiva durante el transcurso de la vida útil de un programa que regularmente pierde memoria, un gran problema. Estos goteos torturarán hasta la muerte el programa y posiblemente el sistema operativo.
Referencias mutuas. Si los objetos A y B se referencian entre sí mediante punteros compartidos, debe hacer algo especial, ya sea en el diseño de esas clases o en el código que implementa / usa esas clases para romper la circularidad. (Esto no es un problema para los idiomas recolectados de basura).
Recordando demasiado Este es el primo malvado de la basura / fugas de memoria. RAII no ayudará aquí, ni la recolección de basura. Este es un problema en cualquier idioma. Si alguna variable activa tiene una ruta que la conecta a un fragmento de memoria aleatorio, ese fragmento de memoria aleatorio no es basura. Hacer que un programa se vuelva olvidadizo para que pueda ejecutarse durante varios días es complicado. Hacer un programa que pueda ejecutarse durante varios meses (por ejemplo, hasta que falle el disco) es muy, muy complicado.
No he tenido un problema grave con las fugas durante mucho, mucho tiempo. El uso de RAII en C ++ ayuda mucho a resolver esos goteos y fugas. (Sin embargo, uno debe tener cuidado con los punteros compartidos). Mucho más importante, he tenido problemas con las aplicaciones cuyo uso de la memoria sigue creciendo y creciendo y creciendo debido a conexiones no cortadas a la memoria que ya no son de ningún uso.
fuente
Depende del programador de C ++ implementar su propia forma de recolección de basura cuando sea necesario. De lo contrario, se producirá lo que se denomina una "pérdida de memoria". Es bastante común que los lenguajes de 'alto nivel' (como Java) se hayan incorporado en la recolección de elementos no utilizados, pero los lenguajes de 'bajo nivel' como C y C ++ no.
fuente