c++ memory-management maintenance

c++ - Consejos para tratar con el mantenimiento del código



memory-management maintenance (14)

He estado trabajando en mi universidad este verano en un laboratorio de imagen / video. Recientemente, mi profesor me dio un programa escrito por un estudiante graduado que acaba de abandonar el programa para "arreglarlo", porque estaba "dando algunos errores".

El proyecto fue escrito en C ++ (parece ser una mala señal recurrente en el código del estudiante). Abrí el proyecto en VS08, ejecuté el proyecto y resultó que los "errores" eran un bad_alloc. Efectivamente, la gestión de la memoria, o más precisamente, la falta de ella, fue el problema.

Al programador pareció gustarle mezclar mallocs, noticias y novedades [] en todo el código, sin ningún tipo de eliminación, eliminación o eliminación []. Para empeorar las cosas, todos los objetos parecen hacer al menos 4-5 cosas no relacionadas. Y para rematar, aquí hay un comentario dejado por el programador:

//do not delete objects, it seems to cause bugs in the segmenter

Por lo que puedo ver, hay una buena mezcla malsana de referencias de punteros y referencias, y todos los valores se cambian pasando por referencia a las funciones de clase monolítica que también pueden ser estáticas. En el momento de la compilación, había alrededor de 23 advertencias, cosas como una posible pérdida de datos al convertir de doble a char, alrededor de 17 variables no utilizadas, etc. Es en momentos como que desearía que C ++ nunca hubiera existido en las universidades, y que todo el trabajo de laboratorio fuera hecho en como python o matlab ...

Así que ahora, el profesor quiere que yo "juegue" con el programa para que pueda ejecutarse en conjuntos de datos alrededor de 10 veces más grande de lo que estaba acostumbrado. Admito que tengo un poco de miedo de decirle que el código es basura.

StackOverflow, ustedes nunca han fallado antes de dar un buen consejo, por lo que ahora les suplico, cualquier consejo sobre cómo lidiar con situaciones como esta sería MUCHO apreciado.

EDITAR El código es alrededor de 5000 LoC

EDIT2 El profesor decidió ir con el enfoque más fácil. Lo que estaba consiguiendo más memoria RAM. Yay por estar a punto de tirar dinero al problema ...


5000 LoC no es tan malo. Considérate afortunado.

Ya que parece que la administración de la memoria es uno de los mayores problemas, yo empezaría allí.

  1. Reemplace cada uso de malloc con new .
  2. Arreglar las advertencias del compilador.
  3. Reemplace los arreglos con vectores (o estructuras de datos más apropiadas).
  4. Reemplace los punteros sin procesar por variables / referencias de pila cuando sea posible, y los punteros inteligentes cuando no. No se preocupe por cambios importantes en la arquitectura o una conversión del 100%; céntrese en la fruta que cuelga y limpie algunas de las principales pérdidas de memoria.
  5. Comience rearchitecting la aplicación.
    1. Elija un comportamiento / tarea discreta en la aplicación.
    2. Averigua aproximadamente cómo funciona.
    3. Averigua cómo quieres que funcione (concentrándote en la interfaz).
    4. Desarrollar un plan de transición.
    5. Ejecuta tu plan.
    6. Repetir.

Claves para el mantenimiento:

  • SCM . En Windows recomendaría algo como Mercurial ( cliente TortoiseHg ). Haga un repositorio central bendecido de donde otros puedan obtener el software y donde las correcciones se comprometan desde los repositorios privados del desarrollador.

  • Problemas / seguimiento de errores. Si aún no tienes uno, consíguelo. Eso debería ser una venta fácil para el prof. No puedo dar ninguna recomendación para Windows. Los amigos están usando Mantis .

  • Gestión de la liberación. A menudo parte del sistema de seguimiento de problemas. Pero el proceso de hacer un lanzamiento en sí mismo es más una cuestión organizativa y política, no técnica. Esencialmente se trata de dar un nombre público a ciertas versiones internas del software. En la vida real se vuelve altamente político cuando se decide qué es lo que realmente entra en el lanzamiento.

5K LoC no es mucho. Tener SCM ayuda a rastrear los cambios y regresar en caso de regresiones. El seguimiento de problemas ayuda al equipo a colaborar en los problemas. Realice lanzamientos menores cuando solucione problemas (p. Ej., Malloc en comparación con nuevas fugas de memoria). Realice lanzamientos importantes para las correcciones y características más grandes (por ejemplo, soporte para conjuntos de datos más grandes).

Es importante no apresurarse y comenzar con pequeños cambios. El SCM moderno permite un montón de pequeñas confirmaciones (lo mismo que el seguimiento de problemas con los árboles de dependencia de problemas) y uno debería usar eso Eso permite rastrear cómo cambia el comportamiento del software y qué cambio introdujo con precisión la regresión. Muy a menudo es el cambio de aspecto más inocente.


Como espacio de seguridad, puede intentar usar algún tipo de implementación de gestión de memoria recolectada como basura dlmalloc para ver si eso le permite superar temporalmente el problema de la falta de memoria.

A la larga, tendrá que solucionar el problema de la gestión de la memoria, simplemente no hay forma de evitarlo. A partir de su descripción, es probable que también tenga que abordar los problemas de diseño del objeto, pero es probable que primero pueda hacer algo de limpieza de administración de memoria.

Esta es la forma en que yo abordaría esto:

  • Encuentra todos los lugares en el código que llaman malloc() . Mírelos bien y trate de reemplazarlos con llamadas a new modo que solo tenga que lidiar con un solo tipo de administración de memoria, lo que facilitará el paso al siguiente paso.
  • Encuentre todos los lugares a los que se llama new y vea si puede reemplazar los punteros en bruto a los que se asignan los resultados de new con boost::shared_ptr<> . Esto debería al menos darte algún tipo de recolección de basura del hombre pobre; por supuesto, también debe cambiar todos los prototipos de funciones de las funciones que reciben estos punteros para tomar shared_ptr<> lugar de punteros sin procesar, de lo contrario, simplemente habrá arreglado el desorden y realmente no habrá mejorado nada. En realidad diría que has cambiado las cosas para peor ...

Una vez que haya abordado los problemas inmediatos de administración de memoria, puede comenzar a refactorizar los objetos y mejorar el diseño. No empezaría por arreglar el diseño primero, es mejor que el software funcione correctamente, que comprenda mejor su funcionamiento y que resuelva el diseño; de lo contrario, es probable que reemplace un lío por otro.


En mi opinión, lo mejor que puede hacer es decirle al profesor que el código es un desastre y que no funcionará como se espera a menos que se vuelvan a escribir algunas partes. Sugiera una refactorización de código pequeño, reescribiendo solo las partes que lo necesitan (no todo el programa).

No contando esto, te puede dar más problemas de los que ya tienes :)


Este problema podría solucionarse fácilmente agregando más RAM. 1-2GB debería hacer el truco.


Explícale al profesor el problema que ves. Y nunca presentar un problema sin una solución. Tenga esto en cuenta al escribir su informe junto con sus sugerencias. Puede ofrecer múltiples posibilidades y dejar que elijan la que desean tomar. Y en ese momento habrás hecho tu trabajo, ¡es hora de que ellos hagan el suyo!


No hay nada que ganar ocultando el problema al profesor. Sí, quejarse de que te dieron un montón de chatarra para trabajar podría sonar como gimoteo, pero esconder el hecho y tratar de arreglarlo en el costado hará que parezca que eres un trabajador lento y, en el peor de los casos, incompetente. Creo que lo primero que debe hacer es tratar de decirle cortésmente al profesor que hay muchos problemas con este programa que deben limpiarse y dar ejemplos ... ¿el profesor sabe algo sobre programación? No creo que eso se haya explicado en la pregunta ... pero de una forma u otra explica el problema y dice qué crees que tomará para solucionarlo.

Dicho esto, la siguiente gran pregunta que me viene a la mente es: ¿la mala gestión de la memoria y las conversiones de tipo malo son los únicos grandes problemas, o son solo ejemplos que escogió de cien problemas serios? Es decir, ¿la estructura y lógica básicas del programa son básicamente sólidas, o todo esto es un desastre? (Supongo que todo es un desastre, pero obviamente no he visto el programa).

La decisión clave que se debe tomar aquí es: ¿Usted (a) repasa el programa y corrige varios errores de detalle de uno en uno? (b) ¿Realiza una seria reestructuración y limpieza? O (c) Tíralo y reescribe desde cero. A menudo me siento tentado a elegir (c) en casos como este porque parece que volver a escribir sería menos trabajo que limpiar el desorden, pero es probable que haya muchas decisiones lógicas detalladas incrustadas en el código existente. A menos que tenga una documentación detallada y actualizada sobre los requisitos, que es una posibilidad tan poco probable de que la rechace al instante en medio de la risa con solo pensarlo, la única forma de conocer todas las reglas es leer el código existente. y averiguar lo que está haciendo. En qué punto (a) o (b) se vuelven mucho más eficientes que (c).

Ojalá pudiera decir "solo haz X", desafortunadamente con la cantidad de información que puedes poner razonablemente en una publicación, creo que todos estamos limitados a decir "Aquí hay algunas opciones posibles para considerar ..."


Primero, el código malo es código malo. Python, Java, Matlab o cualquier otra cosa recogida de basura no es un buen código. Podrías pasar tu tiempo depurando mal el código Python.

Con eso dicho, ser absolutamente honesto con el profesor. Dígale que el código es malo y muéstrele algunos ejemplos. Lo peor que puedes hacer es tratar de ocultar el problema. Si haces eso, el problema no solo es seguro para aterrizar en tu regazo, sino que también es seguro que se te culpará a ti.

Averigua cuál es la mejor solución y propóntela a ella. Puede ser corregir ese código, solicitar la ayuda del departamento de informática o reescribirlo, ya sea en C ++ u otro idioma. Es probable que ella no tenga otras opciones y con gusto aceptará cualquier solución que usted proponga.


Refactorice el código, un cambio a la vez, hasta que tenga sentido para usted. Lo más importante no es la estrategia de codificación exacta, sino que usted entienda lo que está haciendo y por qué.

Probablemente acabarás tocando cada línea de código, así que no trates de hacer las cosas en ningún orden en particular. Arregla lo primero que salta como mal primero, luego pasa a la siguiente, y así en. Excepción: si la persona anterior no usó una estrategia de formato de código consistente, ejecute todo el proceso a través de un autocentro como su primera acción.

Escribe una suite de prueba a medida que avanzas.


Se han dado muchas buenas sugerencias sobre cómo lidiar con el código mismo. Me parece que uno de los problemas subyacentes puede ser que esto fue (comprensiblemente) escrito para una clase que no es de computación por un no programador. Puede sugerirle a su prof que en el futuro consiga a alguien en las clases de transmisión del programador (en colaboración con un cs prof) para que realice la implementación real en estos casos.

Ahora, por supuesto, esto tiene un lado positivo y negativo para todos los involucrados. El usuario final (su profesor) debe poder crear una especificación o al menos articular sus necesidades. Puede que tenga que estar convencida de que hay valor en tomarse el tiempo de lo que preferiría estar haciendo para hacer esto. El equipo de implementación debe ser capaz de trabajar con esa especificación y comunicarse con los usuarios finales. El equipo de implementación se asegurará de que el código esté razonablemente bien escrito si en realidad es un equipo y no un individuo.

Toda esta es una excelente preparación para el "mundo real", así como una oportunidad para la cooperación interdisciplinaria, con aquellos que entienden el dominio del problema trabajando con aquellos que entienden las herramientas.

Ofrece a los estudiantes de cs un ejercicio "real" en lugar de un ejercicio artificial, y debe generar más entusiasmo por su parte. (Por supuesto, dependiendo de la rivalidad departamental y la política de p pequeña, también podría ser un despilfarro en la fabricación o un no titular, pero bueno, soy un optimista ...)


Se honesto. Dígale a su profesor de inmediato que el código es una mierda y por qué. Si lo que mencionaste en tu pregunta es cierto, el código es una mierda.

Cuanto tiempo tienes a mano ¿Entiendes los algoritmos implementados?
5kLoC no es mucho, especialmente cuando se trata de un violín de bajo nivel. Cuando conozca los algoritmos subyacentes y tenga suficiente tiempo disponible, volver a escribirlos en 2k líneas de código fácilmente comprensible podría ser mejor que tratar de solucionarlo.


Suena como un completo desastre. Una refactorización es lo menos que necesitas hacer. ¡La mezcla de novedades y malloc''s es una receta para el desastre!

Creo que probablemente tendrás que reescribir casi todo el asunto. Afortunadamente, 5000 SLOC no es enorme y no debería tomar mucho tiempo.

¡Suena como un buen ejercicio!


Sugeriría seguir algunos de los pasos que Michael Feathers describe en su libro "Trabajando eficazmente con el código heredado". A saber: Obtener el código en prueba tan pronto como sea posible.

Tener pruebas unitarias que realicen la funcionalidad le dará algo que puede refactorizar libremente sin miedo.

Sin embargo, sé que es mucho más fácil decir esto que hacerlo realmente. Lea este capítulo sobre Costuras (partes del código que puede anular / enganchar para permitirle obtener el código bajo prueba más fácilmente): http://www.informit.com/articles/article.aspx?p=359417&seqNum=3 Vea también esto en CppUnit y CppUnitLite, un marco para pruebas de unidad en C ++: http://c2.com/cgi/wiki?CppUnitLite

Ejecuta el código a través de un perfilador de memoria. Vea este enlace SO: https://.com/questions/818673/memory-profiler-for-c Esto le ayudará a rastrear los lugares que necesita para comenzar a colocar declaraciones de borrado / libre. También comenzaría a tratar de hacer que la asignación de memoria sea al menos consistente en la API que utiliza.


Reemplace cada

Type* data = (Type*)malloc(123*sizeof(Type));

con

std::vector<Type> data(123);

Toda la gestión de la memoria eliminado! Muy fácil. Luego simplemente pasa los vectores por valor.