Tengo un código C ++ heredado del que se supone que debo eliminar el código no utilizado. El problema es que la base del código es grande.
¿Cómo puedo saber qué código nunca se llama / nunca se usa?
c++
optimization
dead-code
usuario63898
fuente
fuente
f()
, y una llamada paraf()
resolver inequívocamente a la 1ra, entonces no es posible hacer que esa llamada se resuelva a la 2da simplemente agregando una 3a función llamadaf()
- lo "peor que puede hacer "al agregar que la tercera función es hacer que la llamada se vuelva ambigua y, por lo tanto, evitar que el programa se compile. Me encantaría (= estar horrorizado) ver un contraejemplo.Respuestas:
Hay dos variedades de código no utilizado:
Para el primer tipo, un buen compilador puede ayudar:
-Wunused
(GCC, Clang ) debe advertir sobre variables no utilizadas, incluso el analizador de Clang no utilizado se ha incrementado para advertir sobre variables que nunca se leen (aunque se usen).-Wunreachable-code
(GCC anterior, eliminado en 2010 ) debería advertir sobre los bloques locales a los que nunca se accede (sucede con retornos tempranos o condiciones que siempre se evalúan como verdaderas)catch
bloques no utilizados , porque el compilador generalmente no puede probar que no se lanzará ninguna 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:
gcov
. Tenga en cuenta que se deben pasar banderas específicas 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 unitarias o pruebas de no regresión), el código muerto está necesariamente dentro del código no alcanzado ... y puede comenzar desde aquí.Si está extremadamente interesado en el tema y tiene el tiempo y la inclinación para desarrollar una herramienta por su cuenta, le sugiero que use las bibliotecas de Clang para crear dicha herramienta.
Debido a que 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 anulaciones virtuales que no se utilizan, ya que podrían ser llamadas por código de terceros sobre el que no puede razonar.
fuente
foo()
se marque como "llamado" cuando aparece solo enif (0) { foo(); }
sería una bonificación pero requiere inteligencia adicional.)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é utilizando GCC y GNU ld.
Al compilar la fuente, use
-ffunction-sections
y-fdata-sections
, luego, al vincular el uso-Wl,--gc-sections,--print-gc-sections
. El enlazador ahora enumerará todas las funciones que podrían eliminarse porque nunca fueron llamadas y todas las globales que nunca fueron referenciadas.(Por supuesto, también puede omitir la
--print-gc-sections
parte y dejar que el enlazador elimine las funciones en silencio, pero las mantenga 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 el código muerto en las funciones en vivo también se mantendrán.
Algunas características específicas de C ++ también causarán problemas, en particular:
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á creando 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 (usando, por ejemplo
-fvisibility=hidden
), y luego seleccionar explícitamente las funciones exportadas que necesita exportar.fuente
Bueno, si usas g ++ puedes usar esta bandera
-Wunused
De acuerdo con la documentación:
http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html
Editar : Aquí hay otra bandera útil
-Wunreachable-code
Según la documentación:Actualización : Encontré un tema similar Detección de código muerto en proyecto C / C ++ heredado
fuente
-Wunused
advierte sobre las variables que se declaran (o declaran y definen de una vez) pero que en realidad nunca se usan. Por cierto, es bastante molesto con los guardias con alcance: p Hay una implementación experimental en Clang para que también advierta sobre variables no volátiles que se escriben pero nunca se leen (por Ted Kremenek).-Wunreachable-code
advierte sobre el código dentro de una función que no puede ser alcanzado, puede ser código ubicado después de unathrow
oreturn
estado de cuenta o código de una rama que no se toma (lo que ocurre en el caso de las comparaciones tautológicas) por ejemplo.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 está ejecutando, y le informará qué líneas de código se ejecutaron y cuántas veces, así como cuáles no.
Puede 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 #.
fuente
void func()
en a.cpp, que se usa en b.cpp. ¿Cómo puede verificar el compilador que func () se usa en el programa? Es trabajo de enlazadores.La verdadera respuesta aquí es: nunca se puede saber con certeza.
Al menos, para casos no triviales, no puede estar seguro de haberlo recibido todo. Considere lo siguiente del artículo de Wikipedia sobre código inalcanzable :
Como Wikipedia señala correctamente, un compilador inteligente puede detectar algo como esto. Pero considere una modificación:
¿El compilador captará esto? Tal vez. Pero para hacer eso, tendrá que hacer más que correr
sqrt
contra un valor escalar constante. Tendrá que descubrir que(double)y
siempre será un número entero (fácil), y luego comprender el rango matemático desqrt
para el conjunto de enteros (difícil). Un compilador muy sofisticado podría hacer esto para lasqrt
función, 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. Puede seguir agregando capas de sofisticación a su compilador, pero siempre habrá una forma de introducir un código que será inalcanzable para cualquier conjunto 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 quedaría bloqueada por la lógica de validación en otro lugar. No hay forma 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ás con certeza que capturaste todo a menos que revises el código manualmente después. Incluso entonces, nunca estarás seguro de que no te perdiste nada.
La única solución real, en mi humilde opinión, 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.
fuente
No lo he usado yo mismo, pero cppcheck , afirma encontrar funciones no utilizadas. Probablemente no resolverá el problema completo, pero podría ser un comienzo.
fuente
cppcheck --enable=unusedFunction --language=c++ .
para encontrar estas funciones no utilizadas.Puede intentar usar PC-lint / FlexeLint del software Gimple . Reclama
Lo he usado para análisis estático y lo encontré muy bueno, pero tengo que admitir que no lo he usado para encontrar específicamente un código muerto.
fuente
Mi enfoque normal para encontrar cosas no utilizadas es
watch "make 2>&1"
tiende a hacer el truco en Unix.Este es un proceso algo largo, pero da buenos resultados.
fuente
Marque tantas funciones y variables públicas como privadas o protegidas sin causar un error de compilación, mientras hace 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 invocar desde la misma clase (a menos que haya estúpidos macro u otros trucos para evitar 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á débilmente acoplada. Si su código base no tiene clases pequeñas o tiene un acoplamiento muy apretado, sugiero limpiarlas primero.
Lo siguiente será marcar todas las funciones públicas restantes y hacer un gráfico de llamadas para descubrir la relación entre las clases. Desde este árbol, intente averiguar qué parte de la rama parece recortada.
La ventaja de este método es que puede hacerlo por módulo, por lo que es fácil seguir pasando la prueba de la unidad sin tener un largo período de tiempo cuando tiene una base de código dañada.
fuente
Si está en Linux, es posible que desee buscar
callgrind
una herramienta de análisis de programas C / C ++ que forme parte de lavalgrind
suite, que también contiene herramientas que comprueban las pérdidas de memoria y otros errores de memoria (que también debería usar). 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. Por lo general, se usa para el análisis de rendimiento, pero también produce un gráfico de llamadas para sus aplicaciones, para que pueda ver qué funciones se llaman, así como sus llamadores.Obviamente, esto es complementario a los métodos estáticos mencionados en otra parte de la página, y solo será útil para eliminar clases, métodos y funciones completamente no utilizados; no ayuda a encontrar código muerto dentro de los métodos que realmente se llaman.
fuente
Realmente no he usado ninguna herramienta que haga tal cosa ... Pero, por lo que he visto en todas las respuestas, nadie ha dicho que este problema sea indiscutible.
¿Qué quiero decir con esto? Que este problema no puede ser resuelto por ningún algoritmo en una computadora. Este teorema (que dicho algoritmo no existe) es un corolario del problema de detención de Turing.
Todas las herramientas que usará no son algoritmos sino heurísticas (es decir, no algoritmos exactos). No le darán exactamente todo el código que no se usa.
fuente
Una forma es usar un depurador y la función de compilación 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. Por lo tanto, 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" corresponden al código eliminado, o ese código nunca se llama o se ha insertado y debe realizar un mínimo análisis para encontrar cuál de esos dos sucedió.
Al menos así es como funciona en Visual Studio y supongo que otros conjuntos de herramientas también pueden hacerlo.
Eso es mucho trabajo, pero supongo que es más rápido que analizar manualmente todo el código.
fuente
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 un ejemplo de regla incorporada (personalizable) para detectar métodos muertos, escrita en CQLinq :
fuente
Depende de la plataforma que use para crear su 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 usa 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 liberar 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 obtener ese archivo de registro y echarle un vistazo.
fuente
No creo que se pueda hacer automáticamente.
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 de alto precio, como el compilador de Coverity o LLVM, podría ser de ayuda.
Pero no estoy seguro y preferiría la revisión manual del código.
ACTUALIZADO
Bueno ... solo eliminando variables no utilizadas, las funciones no utilizadas no son difíciles.
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 cobertura.
fuente
Un amigo me hizo esta misma pregunta hoy, y miré a mi alrededor algunos desarrollos prometedores de Clang, por ejemplo, ASTMatcher s y el Analizador estático que podrían tener suficiente visibilidad en el proceso 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 algunos indicadores de GCC que aparentemente están diseñados con el propósito de identificar símbolos sin referencia.
fuente
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 Turing alguna vez se detendrá. Puede obtener si hay alguna ruta (estáticamente) que va desde main () a la función que ha escrito, pero eso no garantiza que alguna vez se llamará.
fuente
Bueno, si usas g ++ puedes usar esta bandera -Wunused
De acuerdo con la documentación:
http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html
Editar: Aquí hay otro indicador útil: Código inalcanzable Según la documentación:
fuente