2010-02-02 18 views
10

Hace poco estuve entrevistando para una posición en C++, y me preguntaron cómo evito crear fugas de memoria. Sé que no di una respuesta satisfactoria a esa pregunta, así que se lo estoy lanzando a ustedes. ¿Cuáles son las mejores formas de protegerse contra fugas de memoria?¿Cómo protegerse contra fugas de memoria?

Gracias!

+0

Usa un recolector de basura (http://www.google.com/search?q=Garbage+collection+c%2B%2B) ...? – kennytm

+2

@KennyTM No, no use un recolector de basura, cuando tenga RAII. Si realmente necesita propiedad compartida, solo use shared_ptr desde C++ 0x o boost actualmente. – AraK

+4

@Kenny: si quieres vivir con los problemas asociados con GC. C++ tiene un mecanismo de control mucho mejor definido llamado punteros inteligentes. –

Respuesta

20

lo que todas las respuestas dadas hasta ahora se reducen a lo siguiente: evitar tener que llamar al.

Cada vez que el programador tiene que llamar al delete, tiene una posible pérdida de memoria. En su lugar, haga que la llamada delete suceda automáticamente. C++ garantiza que los objetos locales tienen sus destructores llamados cuando salen del alcance. Use esa garantía para asegurarse de que las asignaciones de memoria se eliminen automáticamente.

En su forma más general, esta técnica significa que cada asignación de memoria debe envolverse dentro de una clase simple, cuyo constructor asigna la memoria necesaria, y destructor la libera.

Debido a que esta es una técnica muy utilizada y ampliamente aplicable, se han creado clases de punteros inteligentes que reducen la cantidad de código repetitivo. En lugar de asignar memoria, sus constructores toman un puntero a la asignación de memoria ya realizada, y lo almacena. Cuando el puntero inteligente se sale del alcance, puede eliminar la asignación.

Por supuesto, dependiendo del uso, puede ser necesaria una semántica diferente. ¿Simplemente necesita el caso simple, donde la asignación debería durar exactamente mientras dure la clase contenedora? Luego use boost::scoped_ptr o, si no puede usar boost, std::auto_ptr. ¿Tiene un número desconocido de objetos que hacen referencia a la asignación sin conocimiento de cuánto tiempo vivirá cada uno de ellos? Entonces el boost::shared_ptr contado por referencia es una buena solución.

Pero no tiene que utilizar punteros inteligentes. Los contenedores de biblioteca estándar también funcionan. Asignan internamente la memoria necesaria para almacenar copias de los objetos que colocas en ellas, y vuelven a liberar la memoria cuando se eliminan. Por lo tanto, el usuario no tiene que llamar al new o al delete.

Existen innumerables variaciones de esta técnica, cambiando la responsabilidad de crear la asignación de memoria inicial, o cuando se debe realizar la desasignación.

Pero lo que todos tienen en común es la respuesta a su pregunta: RAII idioma: Adquisición de recursos es inicialización. Las asignaciones de memoria son un tipo de recurso. Los recursos deben ser adquiridos cuando un objeto es inicializado y liberado por el objeto itslef, cuando es destruido.

Haga que el alcance de C++ y las reglas de por vida hagan su trabajo por usted.Nunca llame al delete fuera de un objeto RAII, ya sea una clase de contenedor, un puntero inteligente o algún contenedor ad-hoc para una sola asignación. Deje que el objeto maneje el recurso asignado a él.

Si todas las llamadas delete se realizan automáticamente, no hay forma de que las pueda olvidar. Y entonces no hay forma de que puedas perder memoria.

+1

+1 - Como George C. diría: Qué más ? – paercebal

+2

Tenga en cuenta que podría llevar a sesiones de entrevista incómodas con entrevistadores sin sentido del humor: "¿Cómo se evita fugas de memoria?" ... "¡Evite escribir borrar!" ... – paercebal

+1

razón de más para decirlo;) – jalf

2

Utilice todo tipo de punteros inteligentes.

Usa cierta estrategia para crear y eliminar objetos, como quién crea que es el responsable de eliminar.

7

reemplazar nuevo con shared_ptr's. Básicamente RAII. hacer la excepción del código segura. Use el stl en todas partes posible. Si usa punteros de recuento de referencia, asegúrese de que no formen ciclos. SCOPED_EXIT de boost también es muy útil.

+1

Por favor, ilustra cómo reemplazarías 'nuevo' con un puntero compartido. –

+1

boost :: shared_ptr ptr = boost :: shared_ptr (nuevo T()); –

+1

Eso usa nuevo, casi no reemplaza nada. –

1

Una muy buena forma es utilizar punteros inteligentes, el realce/TR1 :: shared_ptr. La memoria se liberará una vez que el puntero inteligente (stack asignado) salga del alcance.

0
  • Smart punteros.
  • Gestión de memoria.
  • Anule 'nuevo' y 'eliminar' o use sus propias macros/plantillas.
3
  1. (Fácil) Nunca deje que un puntero sin poseer un objeto (buscar el código para la expresión regular "\= *new". Utilice shared_ptr o scoped_ptr lugar, o mejor aún, utilizar las variables reales en vez de punteros tan a menudo como sea posible.

  2. (duro) Asegúrese de que usted no tiene ningún referencias circulares, con shared_ptrs apuntando el uno al otro, utilice weak_ptr para romperlos.

¡Hecho!

+0

+1 para las referencias circualr es un problema –

+0

Uhm, ¿Por qué el -1? –

20
  1. No asigne memoria en el montón si no es necesario. La mayor parte del trabajo se puede hacer en la pila, por lo que solo deberías hacer asignaciones de memoria en montón cuando lo necesites.

  2. Si necesita un objeto asignado en el montón que sea propiedad de un único objeto, utilice std::auto_ptr.

  3. Use contenedores estándar, o contenedores de Boost en lugar de inventar el suyo propio.

  4. Si tiene un objeto al que se refieren otros objetos y no es propiedad de nadie en particular, utilice std::tr1::shared_ptr o std::tr1::weak_ptr, el que mejor se adapte a su caso de uso.

  5. Si ninguna de estas cosas coincide con su caso de uso, entonces tal vez use delete.Si finalmente tiene que administrar manualmente la memoria, solo use las herramientas de detección de fuga de memoria para asegurarse de que no está filtrando nada (y, por supuesto, solo tenga cuidado). Sin embargo, nunca deberías llegar a este punto.

+0

-1 Las personas que escriben sus propios contenedores saben lo que están haciendo y no necesitan que se les indique que no los utilicen para las causas de pérdida de memoria. –

+10

@Viktor: No, muchas personas escriben sus propios contenedores sin saber lo que están haciendo. El hecho de que una pequeña minoría de programadores de C++ pueda escribir contenedores correctamente no significa que la gran mayoría no se beneficie al recibir la orden de detenerse y usar los estándar. – jalf

+3

'std :: auto_ptr' está en desuso. Use 'std :: unique_ptr' en su lugar. –

0

en x86 puede utilizar regularmente Valgrind para comprobar su código

2
  • asegúrese de que entiende exactamente cómo cada vez que se elimina un objeto de crear una
  • asegurarse de que entiende que posee el puntero cada vez que uno se le devuelve
  • asegurarse de que sus caminos de error disponer de objetos que ha creado adecuadamente
  • ser paranoico con lo anterior
2

Además del consejo sobre RAII, recuerde hacer que su destructor de la clase base sea virtual si hay funciones virtuales.

+0

+1 para la virtualidad – neuro

2

Para evitar fugas de memoria, lo que debe hacer es tener una idea clara y definitiva de quién es responsable de eliminar cualquier objeto dinámicamente asignado.

C++ permite la construcción de objetos en la pila (es decir, como tipo de variables locales). Esto vincula la creación y la destrucción al flujo de control: un objeto se crea cuando la ejecución del programa alcanza su declaración, y el objeto se destruye cuando la ejecución escapa del bloque en el que se realizó la declaración. Siempre que la asignación necesite coincidir con ese patrón, úsela. Esto te ahorrará gran parte del problema.

Para otros usos, si puede definir y el documento una noción clara de responsabilidad, entonces esto puede funcionar bien. Por ejemplo, tiene un método o una función que devuelve un puntero a un objeto recién asignado, y documenta que la persona que llama se vuelve responsable de eliminar en última instancia esa instancia. La documentación clara junto con una buena disciplina del programador (¡algo que no se logra fácilmente!) Puede resolver muchos problemas restantes de la administración de la memoria.

En algunas situaciones, incluidos los programadores indisciplinados y las complejas estructuras de datos, es posible que deba recurrir a técnicas más avanzadas, como el recuento de referencias. Cada objeto recibe un "contador", que es el número de otras variables que lo señalan. Cuando una parte del código decide no señalar al objeto, el contador disminuye. Cuando el contador llega a cero, el objeto se elimina. El conteo de referencias requiere un manejo estricto del contador. Esto se puede hacer con los llamados "punteros inteligentes": estos son objetos que son punteros funcionales, pero que ajustan automáticamente el contador sobre su propia creación y destrucción.

El recuento de referencias funciona bastante bien en muchas situaciones, pero no pueden manejar estructuras cíclicas. Por lo tanto, para las situaciones más complejas, debe recurrir a la artillería pesada, es decir, a garbage collector. El que yo asocio es el GC para C y C++ escrito por Hans Boehm, y se ha usado en algunos proyectos bastante grandes (por ejemplo, Inkscape). El objetivo de un recolector de basura es mantener una vista global del espacio de memoria completo, para saber si una instancia determinada todavía está en uso o no. Esta es la herramienta adecuada cuando las herramientas de visualización local, como el recuento de referencias, no son suficientes. Uno podría argumentar que, en ese punto, uno debería preguntarse si C++ es el lenguaje correcto para el problema en cuestión. La recolección de basura funciona mejor cuando el lenguaje es cooperativo (esto desbloquea un host de optimizaciones que no son factibles cuando el compilador desconoce lo que sucede con la memoria, como un compilador típico de C o C++).

Tenga en cuenta que ninguna de las técnicas descritas anteriormente le permite al programador dejar de pensar. Incluso un GC puede sufrir fugas de memoria, porque usa accesibilidad como una aproximación de uso futuro (hay razones teóricas que implican que no es posible, en general, detectar con precisión todos los objetos que no se utilizarán). después de eso). Es posible que aún tenga que establecer algunos campos en NULL para informar al GC que ya no podrá acceder a un objeto a través de una variable determinada.

0

Puede usar la utilidad. Si trabajas en Linux, utiliza valgrid (es gratis). Utilice el delegador en Windows.