entre diferencias diferencia dependencia composicion asociación asociacion agregacion c++ optimization dead-code

c++ - diferencias - ¿Cómo puedo saber qué partes del código nunca se usan?



diferencias entre agregacion composicion y asociacion (18)

Tengo un código C ++ heredado del que debo eliminar el código no utilizado. El problema es que el código base es grande.

¿Cómo puedo saber qué código nunca se llama / nunca se usa?


Creo que estás buscando una herramienta de cobertura de código . Una herramienta de cobertura de código analizará su código mientras se ejecuta, y le permitirá saber qué líneas de código se ejecutaron y cuántas veces, así como cuáles no.

Podría intentar darle una oportunidad a esta herramienta de cobertura de código fuente abierto: TestCocoon : herramienta de cobertura de código para C / C ++ y C #.


Depende de la plataforma que uses para crear tu aplicación.

Por ejemplo, si usa Visual Studio, podría usar una herramienta como .NET ANTS Profiler que puede analizar y perfilar su código. De esta manera, debe saber rápidamente qué parte de su código se utiliza realmente. Eclipse también tiene complementos equivalentes.

De lo contrario, si necesita saber qué función de su aplicación utiliza realmente su usuario final, y si puede lanzar su aplicación fácilmente, puede usar un archivo de registro para una auditoría.

Para cada función principal, puede rastrear su uso, y después de unos días / semana, simplemente obtenga ese archivo de registro y eche un vistazo.


El problema general de si se llamará a alguna función es NP-Complete. No puede saber de antemano de manera general si se llamará a alguna función, ya que no sabrá si una máquina de Turing se detendrá alguna vez. Puede obtenerlo si existe algún camino (estáticamente) que va desde main () a la función que ha escrito, pero eso no le garantiza que nunca será llamado.


Hay dos variedades de código no utilizado:

  • la local, es decir, en algunas funciones algunas rutas o variables no se utilizan (o se usan pero no de manera significativa, como escritas pero nunca leídas)
  • el global: funciones que nunca se llaman, objetos globales a los que nunca se accede

Para el primer tipo, un buen compilador puede ayudar:

  • -Wunused (GCC, Clang ) debe advertir acerca de las variables no utilizadas, el analizador no utilizado Clang incluso se ha incrementado para advertir sobre las variables que nunca se leen (aunque se usen).
  • -Wunreachable-code (GCC anterior, eliminado en 2010 ) debe advertir sobre los bloques locales a los que nunca se accede (esto ocurre con los primeros resultados o condiciones que siempre se evalúan como verdaderas)
  • No conozco ninguna opción para advertir sobre bloques catch no utilizados, porque el compilador generalmente no puede probar que no se lanzará una excepción.

Para el segundo tipo, es mucho más difícil. Estáticamente, requiere un análisis completo del programa, y ​​aunque la optimización del tiempo de enlace puede eliminar el código muerto, en la práctica el programa se ha transformado tanto en el momento en que se realiza que es casi imposible transmitir información significativa al usuario.

Por lo tanto, hay dos enfoques:

  • La teórica es utilizar un analizador estático. Una pieza de software que examinará todo el código a la vez con gran detalle y encontrará todas las rutas de flujo. En la práctica no conozco a nadie que funcione aquí.
  • La pragmática es usar una heurística: use una herramienta de cobertura de código (en la cadena GNU es gcov . Tenga en cuenta que se deben pasar indicadores específicos durante la compilación para que funcione correctamente). Ejecuta la herramienta de cobertura de código con un buen conjunto de entradas variadas (sus pruebas de unidad o pruebas de no regresión), el código muerto está necesariamente dentro del código no alcanzado ... y así puede comenzar desde aquí.

Si está muy interesado en el tema, y ​​tiene el tiempo y la inclinación para elaborar una herramienta por su cuenta, le sugiero que utilice las bibliotecas de Clang para crear dicha herramienta.

  1. Utilice la biblioteca Clang para obtener un AST (árbol de sintaxis abstracta)
  2. Realizar un análisis de marcado y barrido desde los puntos de entrada en adelante.

Como Clang analizará el código por usted y realizará una resolución de sobrecarga, no tendrá que lidiar con las reglas de los lenguajes de C ++ y podrá concentrarse en el problema en cuestión.

Sin embargo, este tipo de técnica no puede identificar las sustituciones virtuales que no se utilizan, ya que pueden ser invocadas por un código de terceros sobre el que no puede razonar.


Hice que un amigo me hiciera esta pregunta hoy mismo, y miré a mi alrededor algunos desarrollos prometedores de Clang, por ejemplo, ASTMatcher y el Static Analyzer que podrían tener suficiente visibilidad durante la compilación para determinar las secciones de código muerto, pero luego encontró esto:

https://blog.flameeyes.eu/2008/01/today-how-to-identify-unused-exported-functions-and-variables

¡Es casi una descripción completa de cómo usar algunas marcas de GCC que aparentemente están diseñadas con el propósito de identificar símbolos sin referencia!


La verdadera respuesta aquí es: nunca se puede saber con seguridad.

Al menos, para casos no triviales, no puede estar seguro de haberlo recibido todo. Considere lo siguiente en.wikipedia.org/wiki/Unreachable_code de en.wikipedia.org/wiki/Unreachable_code :

double x = sqrt(2); if (x > 5) { doStuff(); }

Como Wikipedia señala correctamente, un compilador inteligente puede ser capaz de atrapar algo como esto. Pero consideremos una modificación:

int y; cin >> y; double x = sqrt((double)y); if (x != 0 && x < 1) { doStuff(); }

¿Atrapará el compilador esto? Tal vez. Pero para hacer eso, tendrá que hacer más que ejecutar sqrt contra un valor escalar constante. Tendrá que darse cuenta de que (double)y siempre será un entero (fácil), y luego entender el rango matemático de sqrt para el conjunto de enteros (duro). Un compilador muy sofisticado podría hacer esto para la función sqrt , o para cada función en math.h , o para cualquier función de entrada fija cuyo dominio pueda resolver. Esto se vuelve muy, muy complejo, y la complejidad es básicamente ilimitada. Puedes seguir agregando capas de sofisticación a tu compilador, pero siempre habrá una forma de colar algún código que no estará disponible para un conjunto dado de entradas.

Y luego están los conjuntos de entrada que simplemente nunca se ingresan. Entrada que no tendría sentido en la vida real, o que la lógica de validación la bloquee en otro lugar. No hay manera de que el compilador sepa sobre eso.

El resultado final de esto es que, si bien las herramientas de software que otros han mencionado son extremadamente útiles, nunca sabrá con certeza si lo detectó todo, a menos que después revise el código manualmente. Incluso entonces, nunca estarás seguro de que no te perdiste nada.

La única solución real, IMHO, es estar lo más alerta posible, usar la automatización a su disposición, refactorizar donde pueda y buscar constantemente formas de mejorar su código. Por supuesto, es una buena idea hacer eso de todos modos.


Marque tantas funciones y variables públicas como privadas o protegidas sin causar un error de compilación, al hacer esto, intente también refactorizar el código. Al hacer que las funciones sean privadas y, hasta cierto punto, protegidas, redujo su área de búsqueda, ya que las funciones privadas solo se pueden llamar desde la misma clase (a menos que haya una macro estúpida u otros trucos para eludir la restricción de acceso, y si ese es el caso, le recomendaría encontrar un nuevo trabajo). Es mucho más fácil determinar que no necesita una función privada, ya que solo la clase en la que está trabajando actualmente puede llamar a esta función. Este método es más fácil si su base de código tiene clases pequeñas y está ligeramente acoplado. Si su base de código no tiene clases pequeñas o tiene un acoplamiento muy apretado, sugiero que las limpie primero.

Lo siguiente será marcar todas las funciones públicas restantes y hacer un gráfico de llamadas para averiguar la relación entre las clases. Desde este árbol, trate de averiguar qué parte de la rama parece que se puede recortar.

La ventaja de este método es que puede hacerlo por módulo, por lo que es fácil pasar la prueba de la unidad sin tener un gran período de tiempo cuando tiene una base de código rota.


Mi enfoque normal para encontrar cosas no utilizadas es

  1. Asegúrese de que el sistema de compilación maneje el seguimiento de dependencia correctamente
  2. configure un segundo monitor, con una ventana de terminal de pantalla completa, ejecutando compilaciones repetidas y mostrando la primera pantalla llena de resultados. watch "make 2>&1" tiende a hacer el truco en Unix.
  3. ejecuta una operación de buscar y reemplazar en todo el árbol de origen, agregando "//?" al principio de cada línea
  4. arregle el primer error marcado por el compilador, eliminando "//?" en las líneas correspondientes.
  5. Repita hasta que no quede ningún error.

Este es un proceso un tanto largo, pero da buenos resultados.


No creo que se pueda hacer de forma automática.

Incluso con las herramientas de cobertura de código, debe proporcionar suficientes datos de entrada para ejecutar.

Puede ser una herramienta de análisis estático muy compleja y Coverity''s , como la del Coverity''s de Coverity''s o LLVM, que podría ser de ayuda.

Pero no estoy seguro y preferiría la revisión manual del código.

ACTUALIZADO

Bueno ... solo eliminar las variables no utilizadas, las funciones no utilizadas no es difícil.

ACTUALIZADO

Después de leer otras respuestas y comentarios, estoy más convencido de que no se puede hacer.

Debe conocer el código para tener una medida de cobertura de código significativa, y si sabe que mucha edición manual será más rápida que preparar / ejecutar / revisar los resultados de la cobertura.


No lo he usado, pero cppcheck , afirma que encuentra funciones no utilizadas. Probablemente no resolverá el problema completo, pero podría ser un comienzo.


Para el caso de funciones completas no utilizadas (y variables globales no utilizadas), GCC puede hacer la mayor parte del trabajo por usted siempre que esté usando GCC y GNU ld.

Al compilar la fuente, use -ffunction-sections y -fdata-sections , luego cuando vincule use -Wl,--gc-sections,--print-gc-sections . El enlazador ahora mostrará una lista de todas las funciones que podrían eliminarse porque nunca fueron llamadas y todas las globales a las que nunca se hizo referencia.

(Por supuesto, también puede omitir la parte --print-gc-sections y dejar que el enlazador elimine las funciones en silencio, pero --print-gc-sections en la fuente).

Nota: esto solo encontrará funciones completas no utilizadas, no hará nada sobre el código muerto dentro de las funciones. Las funciones llamadas desde código muerto en funciones en vivo también se mantendrán alrededor.

Algunas características específicas de C ++ también causarán problemas, en particular:

  • Funciones virtuales. Sin saber qué subclases existen y cuáles realmente se crean instancias en el tiempo de ejecución, no puede saber qué funciones virtuales necesita para existir en el programa final. El enlazador no tiene suficiente información al respecto, por lo que tendrá que mantenerlos a todos.
  • Globales con constructores, y sus constructores. En general, el enlazador no puede saber que el constructor de un global no tiene efectos secundarios, por lo que debe ejecutarlo. Obviamente, esto significa que el global en sí también necesita ser mantenido.

En ambos casos, cualquier cosa utilizada por una función virtual o un constructor de variable global también debe mantenerse.

Una advertencia adicional es que si está construyendo una biblioteca compartida, la configuración predeterminada en GCC exportará todas las funciones de la biblioteca compartida, haciendo que se "use" en lo que respecta al vinculador. Para solucionarlo, debe establecer el valor predeterminado para ocultar símbolos en lugar de exportar (mediante, por ejemplo, -fvisibility=hidden ), y luego seleccionar explícitamente las funciones exportadas que necesita exportar.


Puede intentar usar PC-lint / FlexeLint de Gimple Software . Afirma que

Encuentre macros no utilizadas, typedef''s, clases, miembros, declaraciones, etc. en todo el proyecto.

Lo usé para análisis estático y lo encontré muy bueno, pero debo admitir que no lo he usado para encontrar específicamente el código muerto.


Pues si usas g ++ puedes usar esta bandera -Wunused

Según la documentación:

Warn whenever a variable is unused aside from its declaration, whenever a function is declared static but never defined, whenever a label is declared but not used, and whenever a statement computes a result that is explicitly not used.

http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html

Edición: Aquí hay otra bandera útil: código de acuerdo con el código de conexión:

This option is intended to warn when the compiler detects that at least a whole line of source code will never be executed, because some condition is never satisfied or because it is after a procedure that never returns.


Pues si usas g ++ puedes usar esta bandera -Wunused

Según la documentación:

Avisar cuando una variable no se utiliza, aparte de su declaración, siempre que una función se declara estática pero nunca se define, siempre que una etiqueta se declara pero no se usa, y siempre que una declaración calcula un resultado que no se usa explícitamente.

http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html

Edición : Aquí hay otro indicador útil -Wunreachable-code según la documentación:

Esta opción tiene la intención de advertir cuando el compilador detecta que al menos una línea completa de código fuente nunca se ejecutará, porque alguna condición nunca se cumple o porque es posterior a un procedimiento que nunca regresa.

Actualización : encontré un tema similar Detección de código muerto en un proyecto de C / C ++ heredado


Realmente no he usado ninguna herramienta que haga tal cosa ... Pero, por lo que he visto en todas las respuestas, nadie ha dicho nunca que este problema es incuestionable.

¿Qué quiero decir con esto? Que este problema no puede ser resuelto por ningún algoritmo en una computadora. Este teorema (que tal algoritmo no existe) es un corolario del problema de la detención de Turing.

Todas las herramientas que utilizará no son algoritmos sino heurísticas (es decir, no son algoritmos exactos). No le darán exactamente todo el código que no se utiliza.


Si está en Linux, es posible que desee buscar en callgrind , una herramienta de análisis de programas C / C ++ que forma parte de la suite valgrind , que también contiene herramientas que comprueban las pérdidas de memoria y otros errores de memoria (que también debería estar usando). ). Analiza una instancia en ejecución de su programa y produce datos sobre su gráfico de llamadas y sobre los costos de rendimiento de los nodos en el gráfico de llamadas. Normalmente se usa para el análisis del rendimiento, pero también produce un gráfico de llamadas para sus aplicaciones, de modo que puede ver a qué funciones se llama, así como a sus llamadores.

Obviamente, esto es complementario a los métodos estáticos mencionados en otras partes de la página, y solo será útil para eliminar clases, métodos y funciones que no se usen en su totalidad. No ayudará a encontrar código muerto dentro de los métodos a los que realmente se llama.


Una forma es usar un depurador y la función del compilador para eliminar el código de máquina no utilizado durante la compilación.

Una vez que se elimina algún código de máquina, el depurador no le permitirá poner un breakpojnt en la línea correspondiente del código fuente. Entonces, coloca puntos de interrupción en todas partes e inicia el programa e inspecciona los puntos de interrupción (aquellos que están en el estado "sin código cargado para esta fuente" corresponde al código eliminado), ya sea ese código nunca se llama o está en línea y tiene que realizar un mínimo Análisis para encontrar cuál de los dos sucedió.

Al menos así es como funciona en Visual Studio y creo que otros conjuntos de herramientas también pueden hacer eso.

Eso es mucho trabajo, pero creo que es más rápido que analizar manualmente todo el código.


CppDepend es una herramienta comercial que puede detectar tipos, métodos y campos no utilizados y hacer mucho más. Está disponible para Windows y Linux (pero actualmente no tiene soporte de 64 bits) y viene con una prueba de 2 semanas.

Descargo de responsabilidad: no trabajo allí, pero tengo una licencia para esta herramienta (así como NDepend , que es una alternativa más poderosa para el código .NET).

Para aquellos que tienen curiosidad, aquí hay una regla incorporada (personalizable) de ejemplo para detectar métodos muertos, escrita en CQLinq :

// <Name>Potentially dead Methods</Name> warnif count > 0 // Filter procedure for methods that should''nt be considered as dead let canMethodBeConsideredAsDeadProc = new Func<IMethod, bool>( m => !m.IsPublic && // Public methods might be used by client applications of your Projects. !m.IsEntryPoint && // Main() method is not used by-design. !m.IsClassConstructor && !m.IsVirtual && // Only check for non virtual method that are not seen as used in IL. !(m.IsConstructor && // Don''t take account of protected ctor that might be call by a derived ctors. m.IsProtected) && !m.IsGeneratedByCompiler ) // Get methods unused let methodsUnused = from m in JustMyCode.Methods where m.NbMethodsCallingMe == 0 && canMethodBeConsideredAsDeadProc(m) select m // Dead methods = methods used only by unused methods (recursive) let deadMethodsMetric = methodsUnused.FillIterative( methods => // Unique loop, just to let a chance to build the hashset. from o in new[] { new object() } // Use a hashet to make Intersect calls much faster! let hashset = methods.ToHashSet() from m in codeBase.Application.Methods.UsedByAny(methods).Except(methods) where canMethodBeConsideredAsDeadProc(m) && // Select methods called only by methods already considered as dead hashset.Intersect(m.MethodsCallingMe).Count() == m.NbMethodsCallingMe select m) from m in JustMyCode.Methods.Intersect(deadMethodsMetric.DefinitionDomain) select new { m, m.MethodsCallingMe, depth = deadMethodsMetric[m] }