¿Qué le sucede a la basura en C ++?

51

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?

Ju Shua
fuente
38
Nota: stop-the-world es una opción de implementación de algunos recolectores de basura, pero ciertamente no todos. Hay GC concurrentes, por ejemplo, que se ejecutan simultáneamente con el mutador (eso es lo que los desarrolladores de GC llaman el programa real). Creo que puedes comprar una versión comercial del JVM J9 de código abierto de IBM que tiene un colector sin pausa concurrente. Azul Zing tiene un colector "sin pausa" que no es realmente sin pausa sino extremadamente rápido para que no haya pausas notables (sus pausas GC están en el mismo orden que un cambio de contexto de subproceso del sistema operativo, que generalmente no se ve como una pausa) .
Jörg W Mittag
14
La mayoría de los programas (largo) que se ejecutan en C ++ que utilizo hacer que el uso de memoria que crece sobre el tiempo ilimitadamente. ¿Es posible que no tenga la costumbre de dejar los programas abiertos durante más de unos pocos días a la vez?
Jonathan Cast
12
Tenga en cuenta que con C ++ moderno y sus construcciones ya no necesita eliminar la memoria manualmente (a menos que busque una optimización especial), porque puede administrar la memoria dinámica a través de punteros inteligentes. Obviamente, agrega algo de sobrecarga al desarrollo de C ++ y debe ser un poco más cuidadoso, pero no es algo completamente diferente, solo necesita recordar usar la construcción del puntero inteligente en lugar de simplemente llamar al manual new.
Andy
99
Tenga en cuenta que todavía es posible tener pérdidas de memoria en un lenguaje recolectado de basura. No estoy familiarizado con Java, pero desafortunadamente las pérdidas de memoria son bastante comunes en el mundo administrado de GC de .NET. Los objetos a los que un campo estático hace referencia indirectamente no se recopilan automáticamente, los controladores de eventos son una fuente muy común de fugas, y la naturaleza no determinista de la recolección de basura hace que no sea posible obviar por completo la necesidad de liberar recursos manualmente (lo que lleva a IDisposable modelo). Dicho todo esto, el modelo de administración de memoria C ++ utilizado correctamente es muy superior a la recolección de basura.
Cody Gray
26
What happens to garbage in C++? ¿No se suele compilar en un ejecutable?
BJ Myers

Respuestas:

100

El programador es responsable de garantizar que los objetos que crearon newse eliminen mediante delete. 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 .

int main()
{
    int* variableThatIsAPointer = new int;
    int variableInt = 0;

    delete variableThatIsAPointer;
}

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, variableThatIsAPointercomo 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í, hacemos deleteeste 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:

class Foo
{
public:
    int bar; // Will be deleted when Foo is deleted
    int* otherBar; // Still need to call delete
}

Aquí, se aplica el mismo principio. No tenemos que preocuparnos por barcuándo Foose elimina. Sin embargo otherBar, solo se elimina el puntero. Si otherBares el único puntero válido a cualquier objeto al que apunte, probablemente deberíamos deletehacerlo en Fooel destructor. Este es el concepto de conducción detrás de RAII

La asignación de recursos (adquisición) se realiza durante la creación de objetos (específicamente la inicialización), por el constructor, mientras que la desasignación de recursos (liberación) se realiza durante la destrucción de objetos (específicamente la finalización), por el destructor. Por lo tanto, se garantiza que el recurso se mantendrá entre cuando finalice la inicialización y comience la finalización (mantener los recursos es una clase invariable), y se mantendrá solo cuando el objeto esté vivo. Por lo tanto, si no hay fugas de objetos, no hay fugas de recursos.

RAII 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_ptry std::weak_ptr; aunque he visto y utilizado otros shared_ptr/ weak_ptrimplementaciones que siguen los mismos conceptos. Para estos, un contador de referencia rastrea la cantidad de punteros que hay para un objeto dado, y automáticamente deleteenví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.

Thebluefish
fuente
44
eliminado a través de delete- eso es lo que estaba buscando. Increíble.
Ju Shua
3
Es posible que desee agregar acerca de los mecanismos de alcance proporcionados en c ++ que permiten que gran parte de los nuevos y eliminar se hagan en su mayoría automáticos.
whatsisname
99
@whatsisname no es tan nuevo y eliminar se hacen automáticamente, es que no ocurren en muchos casos
Caleth
10
Los punteros inteligentes lodelete solicitan automáticamente si los usa, por lo que debe considerar usarlos cada vez que no se pueda usar un almacenamiento automático.
Marian Spanik
11
@JuShua Tenga en cuenta que al escribir C ++ moderno, nunca debería necesitar tener deleteel código de su aplicación (y de C ++ 14 en adelante, lo mismo con new), sino usar punteros inteligentes y RAII para eliminar los objetos del montón. std::unique_ptrtipo y std::make_uniquefunción son el reemplazo directo y más simple de newy deletea nivel de código de aplicación.
hyde
82

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".

John R. Strohm
fuente
22
Por supuesto hecho de que su respuesta no contiene ninguna basura o bien, ni repetitivo ...
leftaroundabout
15
@leftaroundabout: Gracias. Lo considero un cumplido.
John R. Strohm
1
OK, esta respuesta libre de basura tiene una palabra clave para buscar: pérdida de memoria. También sería bueno mencionar de alguna manera newy delete.
Ruslan
44
@Ruslan Lo mismo se aplica a mallocy freeo new[]y delete[], o cualquier otro asignadores (como Windows de GlobalAlloc, LocalAlloc, SHAlloc, CoTaskMemAlloc, VirtualAlloc, HeapAlloc, ...), y la memoria asignada para usted (por ejemplo, a través fopen).
user253751
43

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:

int global = 0; // automatic storage

int foo(int a, int b) {
    static int local = 1; // automatic storage

    int c = a + b; // automatic storage

    return c;
}

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 mallocy recupera tradicionalmente con free. En C ++, la memoria se asigna newy recupera tradicionalmente con delete.

C no ha cambiado mucho a lo largo de los años, sin embargo, C ++ moderno evita newy deletecompletamente y depende de las instalaciones de la biblioteca (que ellos mismos usan newy de manera deleteapropiada):

  • Los punteros inteligentes son los más famosos: std::unique_ptrystd::shared_ptr
  • pero los contenedores están mucho más extendidos en realidad: std::string, std::vector, std::map, ... gestionar toda internamente memoria asignada dinámicamente de forma transparente

Hablando de shared_ptreso, 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 por shared_ptrcompleto 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, deleteo std::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:

int main() {
    std::vector<int> vec;
    vec.push_back(1);     // vec: [1]

    int& a = vec.back();

    vec.pop_back();       // vec: [], "a" is now dangling

    std::cout << a << "\n";
}

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.

Matthieu M.
fuente
17
Re: "Usar un puntero colgante, o referencia, es Comportamiento indefinido . En general, afortunadamente, esto es un choque inmediato": ¿En serio? Eso no coincide con mi experiencia en absoluto; Por el contrario, mi experiencia es que los usos de un puntero colgante casi nunca provocan un bloqueo inmediato. . .
ruakh
99
Sí, dado que para estar "colgando", un puntero debe haber apuntado a la memoria previamente asignada en un punto, y es poco probable que esa memoria no haya sido mapeada por completo del proceso, de modo que ya no sea accesible en absoluto, porque será un buen candidato para la reutilización inmediata ... en la práctica, los punteros colgantes no causan accidentes, causan caos.
Leushenko
2
"Como resultado, las pérdidas de memoria no son un problema en C ++". Claro que lo son, siempre hay enlaces C a las bibliotecas para fastidiar, así como recursive shared_ptrs o incluso recursive unique_ptrs, y otras situaciones.
Mooing Duck
3
"No es un problema en C ++, incluso para los nuevos usuarios", lo calificaría como "nuevos usuarios que no provienen de un lenguaje similar a Java o C ".
Leftaroundabout
3
@leftaroundabout: está calificado "siempre que se abstengan de usar new, deletey shared_ptr"; sin newy shared_ptrusted 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.
Matthieu M.
27

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.

gbjbaanb
fuente
99
Los punteros compartidos (que usan RAII) proporcionan una forma moderna de crear fugas. Suponga que los objetos A y B se referencian entre sí a través de punteros compartidos, y nada más hace referencia al objeto A o al objeto B. El resultado es una fuga. Esta referencia mutua no es un problema en idiomas con recolección de basura.
David Hammen
@DavidHammen seguro, pero a costa de atravesar casi todos los objetos para asegurarse. Su ejemplo de punteros inteligentes ignora el hecho de que el puntero inteligente en sí mismo quedará fuera de alcance y luego los objetos serán liberados. Asume que un puntero inteligente es como un puntero, no lo es, es un objeto que se pasa en la pila como la mayoría de los parámetros. Esto no es muy diferente a las pérdidas de memoria causadas en los lenguajes GC. Por ejemplo, el famoso en el que eliminar un controlador de eventos de una clase de interfaz de usuario lo deja en silencio y, por lo tanto, con fugas.
gbjbaanb
1
@gbjbaanb en el ejemplo con los punteros inteligentes, ni puntero inteligente nunca se sale del ámbito, es por eso que hay una fuga. Dado que los dos objetos de puntero inteligente se asignan en un ámbito dinámico , no léxico, cada uno intenta esperar al otro antes de destruirlo. El hecho de que los punteros inteligentes sean objetos reales en C ++ y no solo punteros es exactamente lo que causa la fuga aquí: los objetos punteros inteligentes adicionales en los ámbitos de pila que también apuntaban a los objetos contenedor no pueden desasignarlos cuando se destruyen a sí mismos porque el recuento es distinto de cero
Leushenko
2
La forma .NET no es tirarlo al suelo. Simplemente lo mantiene donde estaba hasta que llega la criada. Y debido a la forma en que .NET asigna memoria en la práctica (no contractual), el montón es más como una pila de acceso aleatorio. Es como tener una pila de contratos y papeles, y revisarlos de vez en cuando para descartar aquellos que ya no son válidos. Y para hacer esto más fácil, los que sobreviven a cada descarte son promovidos a una pila diferente, para que puedas evitar atravesar todas las pilas la mayor parte del tiempo, a menos que la primera pila sea lo suficientemente grande, la criada no toque a las otras.
Luaan
@Luaan fue una analogía ... Creo que sería más feliz si dijera que deja las latas sobre la mesa hasta que la criada venga a limpiar.
gbjbaanb
26

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:

class MyObject {
    public: int x;
};

int objTest()
{
    MyObject obj;
    obj.x = 5;
    return obj.x;
}

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:

class MyList {        
public:
    // a fixed-size pointer to the actual memory.
    int* listOfInts; 
    // constructor: get memory
    MyList(size_t numElements) { listOfInts = new int[numElements]; }
    // destructor: free memory
    ~MyList() { delete[] listOfInts; }
};

int listTest()
{
    MyList list(1024);
    list.listOfInts[200] = 5;
    return list.listOfInts[200];
    // When MyList goes off stack here, its destructor is called and frees the memory.
}

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:

class MyDerivedObject : public MyObject {
    public: int y;
};
std::unique_ptr<MyObject> createObject()
{
    // actually creates an object of a derived class,
    // but the user doesn't need to know this.
    return std::make_unique<MyDerivedObject>();
}

int dynamicObjTest()
{
    std::unique_ptr<MyObject> obj = createObject();
    obj->x = 5;
    return obj->x;
    // At scope end, the unique_ptr automatically removes the object it contains,
    // calling its destructor if it has one.
}

Hay otro tipo de puntero inteligente std::shared_ptrpara 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 deletellamadas 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 ++:

void criticalSection() {
    std::scoped_lock lock(myMutex); // scoped_lock locks the mutex
    doSynchronizedStuff();
} // myMutex is released here automatically

Otros idiomas hacen que esto sea mucho más complicado, ya que requieren que lo haga manualmente (por ejemplo, en una finallyclá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.

Felix Dombek
fuente
77
Esta es la única respuesta que no desinforma a las personas ni pinta C ++ más difícil o peligroso de lo que realmente es.
Alexander Revo
66
Por cierto, solo se considera una mala práctica usar un puntero sin procesar como propietarios de recursos. No hay nada malo en usarlos si apuntan a algo que garantice sobrevivir al puntero mismo.
Alexander Revo
8
Yo segundo Alexander. Estoy desconcertado al ver que "C ++ no tiene administración de memoria automatizada, olvídate deletey 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í?
Quentin
8

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 *alloctenga un correspondiente en freealguna 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:

/**
 * Allocate space for an array of arrays; returns NULL
 * on error.
 */
int **newArr( size_t rows, size_t cols )
{
  int **arr = malloc( sizeof *arr * rows );
  size_t i;

  if ( arr ) // malloc returns NULL on failure
  {
    for ( i = 0; i < rows; i++ )
    {
      arr[i] = malloc( sizeof *arr[i] * cols );
      if ( !arr[i] )
      {
        /**
         * Whoopsie; we can't allocate any more memory for some reason.
         * We can't just return NULL at this point since we'll lose access
         * to the previously allocated memory, so we branch to some cleanup
         * code to undo the allocations made so far.  
         */
        goto cleanup;
      }
    }
  }
  goto done;

/**
 * We encountered a failure midway through memory allocation,
 * so we roll back all previous allocations and return NULL.
 */
cleanup:
  while ( i )         // this is why we didn't limit the scope of i to the for loop
    free( arr[--i] ); // delete previously allocated rows
  free( arr );        // delete arr object
  arr = NULL;

done:
  return arr;
}

Este código es feo con esos gotos, 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 que gotoes realmente una opción atractiva; de lo contrario, está utilizando un grupo de banderas y ifdeclaraciones adicionales .

Puedes hacerte la vida más fácil escribiendo funciones dedicadas de asignación / reparto para cada recurso, algo así como

Foo *newFoo( void )
{
  Foo *foo = malloc( sizeof *foo );
  if ( foo )
  {
    foo->bar = newBar();
    if ( !foo->bar ) goto cleanupBar;
    foo->bletch = newBletch(); 
    if ( !foo->bletch ) goto cleanupBletch;
    ...
  }
  goto done;

cleanupBletch:
  deleteBar( foo->bar );
  // fall through to clean up the rest

cleanupBar:
  free( foo );
  foo = NULL;

done:
  return foo;
}

void deleteFoo( Foo *f )
{
  deleteBar( f->bar );
  deleteBletch( f->bletch );
  free( f );
}
John Bode
fuente
1
Esta es una buena respuesta, incluso con las gotodeclaraciones. 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 de gotodeclaraciones y que no tiene fugas.
David Hammen
"sin tener que rescatar completamente" -> para ser justos, si quieres hablar de C, esta es probablemente una buena práctica. C es un lenguaje que se utiliza mejor para manejar bloques de memoria que provienen de otro lugar o para dividir pequeños fragmentos de memoria en otros procedimientos, pero preferiblemente no hacer ambas cosas al mismo tiempo de forma intercalada. Si está utilizando "objetos" clásicos en C, es probable que no esté utilizando el lenguaje para sus fortalezas.
Leushenko
El segundo gotoes extraño. Sería más legible si cambiaras goto done;a return arr;y arr=NULL;done:return arr;para return NULL;. Aunque en casos más complicados puede haber múltiples gotos, comenzando a desenrollarse en diferentes niveles de preparación (lo que se haría al desenrollar la pila de excepciones en C ++).
Ruslan
2

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.

David Hammen
fuente
-6

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.

xDr_Johnx
fuente