¿Cómo debo detectar archivos #include innecesarios en un proyecto grande de C ++?

96

Estoy trabajando en un gran proyecto de C ++ en Visual Studio 2008 y hay muchos archivos con #includedirectivas innecesarias . A veces, los #includes son solo artefactos y todo se compilará bien con ellos eliminados, y en otros casos, las clases podrían declararse hacia adelante y el #include podría moverse al .cpparchivo. ¿Existen buenas herramientas para detectar ambos casos?

caótico
fuente

Respuestas:

50

Si bien no revelará archivos de inclusión innecesarios, Visual Studio tiene una configuración /showIncludes(haga clic con el botón derecho en un .cpparchivo Properties->C/C++->Advanced) que generará un árbol de todos los archivos incluidos en el momento de la compilación. Esto puede ayudar a identificar archivos que no deberían ser incluidos.

También puede echar un vistazo al idioma pimpl para que pueda salirse con la suya con menos dependencias de archivos de encabezado para que sea más fácil ver el cruft que puede eliminar.

Eclipse
fuente
1
/ showincludes es genial. Hacer esto manualmente era abrumador sin eso.
Shambolic
30

PC Lint funciona bastante bien para esto, y también encuentra todo tipo de problemas tontos para usted. Tiene opciones de línea de comando que se pueden usar para crear herramientas externas en Visual Studio, pero descubrí que es más fácil trabajar con el complemento Visual Lint . Incluso la versión gratuita de Visual Lint ayuda. Pero dale una oportunidad a PC-Lint. Configurarlo para que no le dé demasiadas advertencias lleva un poco de tiempo, pero se sorprenderá de lo que aparece.

Joe
fuente
3
Algunas instrucciones sobre cómo hacer esto con pc-lint se pueden encontrar en riverblade.co.uk/…
David Sykes
26

!!¡¡DESCARGO DE RESPONSABILIDAD!! Trabajo en una herramienta comercial de análisis estático (no en PC Lint). !!¡¡DESCARGO DE RESPONSABILIDAD!!

Hay varios problemas con un enfoque simple sin análisis:

1) Conjuntos de sobrecarga:

Es posible que una función sobrecargada tenga declaraciones que provengan de diferentes archivos. ¡Puede ser que la eliminación de un archivo de encabezado dé como resultado que se elija una sobrecarga diferente en lugar de un error de compilación! El resultado será un cambio silencioso en la semántica que puede ser muy difícil de rastrear después.

2) Especializaciones en plantillas:

Al igual que en el ejemplo de sobrecarga, si tiene especializaciones parciales o explícitas para una plantilla, desea que todas estén visibles cuando se utilice la plantilla. Es posible que las especializaciones para la plantilla principal se encuentren en diferentes archivos de encabezado. Eliminar el encabezado con la especialización no causará un error de compilación, pero puede resultar en un comportamiento indefinido si esa especialización hubiera sido seleccionada. (Ver: Visibilidad de la especialización de plantilla de la función C ++ )

Como lo señaló 'msalters', realizar un análisis completo del código también permite analizar el uso de la clase. Al verificar cómo se usa una clase a través de una ruta específica de archivos, es posible que la definición de la clase (y por lo tanto todas sus dependencias) se pueda eliminar por completo o al menos mover a un nivel más cercano a la fuente principal en la inclusión árbol.

Richard Corden
fuente
@RichardCorden: Su software (QA C ++) es demasiado caro.
Xander Tulip
13
@XanderTulip: Es difícil responder a esto sin terminar en un argumento de venta, así que me disculpo de antemano. En mi humilde opinión, lo que debe considerar es cuánto tiempo le tomaría a un buen ingeniero encontrar cosas como esta (así como muchos otros errores de flujo de control / lenguaje) en cualquier proyecto de tamaño razonable. A medida que el software cambia, la misma tarea debe repetirse una y otra vez. Entonces, cuando calcula la cantidad de tiempo ahorrado, el costo de la herramienta probablemente no sea significativo.
Richard Corden
10

No conozco ninguna de estas herramientas y he pensado en escribir una en el pasado, pero resulta que este es un problema difícil de resolver.

Supongamos que su archivo fuente incluye ah y bh; ah contiene #define USE_FEATURE_Xy bh utiliza #ifdef USE_FEATURE_X. Si #include "a.h"está comentado, su archivo aún puede compilarse, pero es posible que no haga lo que espera. Detectar esto programáticamente no es trivial.

Cualquiera que sea la herramienta que haga esto, también necesitará conocer su entorno de compilación. Si ah se parece a:

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

Entonces USE_FEATURE_Xsolo se define si WINNTestá definido, por lo que la herramienta necesitaría saber qué directivas genera el propio compilador, así como cuáles se especifican en el comando de compilación en lugar de en un archivo de encabezado.

Graeme Perrow
fuente
9

Como Timmermans, no estoy familiarizado con ninguna herramienta para esto. Pero he conocido programadores que escribieron un script en Perl (o Python) para intentar comentar cada línea incluida una a la vez y luego compilar cada archivo.


Parece que ahora Eric Raymond tiene una herramienta para esto .

El cpplint.py de Google tiene una regla "incluye lo que usas" (entre muchas otras), pero hasta donde yo sé, no "incluye solo lo que usas". Aun así, puede resultar útil.

Max Lybbert
fuente
Tuve que reírme cuando leí este. Mi jefe hizo esto mismo en uno de nuestros proyectos el mes pasado. El encabezado reducido incluye por un par de factores.
Don Wakefield
2
codewarrior en Mac solía tener un script integrado para hacer esto, comentar, compilar, en caso de error, deshacer comentarios, continuar hasta el final de #includes. Solo funcionó para #includes en la parte superior de un archivo, pero generalmente es ahí donde están. No es perfecto, pero mantiene las cosas razonablemente cuerdas.
slycrel
5

Si está interesado en este tema en general, es posible que desee consultar el diseño de software C ++ a gran escala de Lakos . Es un poco anticuado, pero entra en muchos problemas de "diseño físico", como encontrar el mínimo absoluto de encabezados que deben incluirse. Realmente no he visto este tipo de cosas discutidas en ningún otro lugar.

Adrian
fuente
4

Prueba Include Manager . Se integra fácilmente en Visual Studio y visualiza sus rutas de inclusión, lo que le ayuda a encontrar cosas innecesarias. Internamente usa Graphviz pero hay muchas más funciones interesantes. Y aunque es un producto comercial tiene un precio muy bajo.

Alex
fuente
3

Si sus archivos de encabezado generalmente comienzan con

#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#endif

(en lugar de usar #pragma una vez) puedes cambiar eso a:

#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#else 
#pragma message("Someheader.h superfluously included")
#endif

Y dado que el compilador genera el nombre del archivo cpp que se está compilando, eso le permitiría saber al menos qué archivo cpp está causando que el encabezado ingrese varias veces.

Sam
fuente
12
Creo que está bien incluir encabezados varias veces. Es bueno incluir lo que usa y no depender de sus archivos de inclusión para hacerlo. Creo que lo que quiere el OP es encontrar #includes que no se utilizan realmente.
Ryan Ginstrom
12
En mi opinión, algo activamente incorrecto que hacer. Los encabezados deben incluir otros encabezados cuando no funcionarían sin ellos. Y cuando se tiene A.hy B.hque tanto depende de C.hy se incluye A.hy B.h, porque se necesita tanto, se va a incluir C.hdos veces, pero eso está bien, porque el compilador omitirá la segunda vez, y si no lo hizo, usted tiene que recordar incluir siempre C.hantes A.ho B.hterminar en inclusiones mucho más inútiles.
Jan Hudec
5
El contenido es preciso, esta es una buena solución para encontrar encabezados que se incluyen varias veces. Sin embargo, esto no responde a la pregunta original y no puedo imaginar cuándo sería una buena idea. Los archivos Cpp deben incluir todos los encabezados de los que dependen, incluso si el encabezado se incluye antes en otro lugar. No desea que su proyecto sea específico de un orden de compilación o asuma que un encabezado diferente incluirá el que necesita.
jaypb
3

De hecho, PC-Lint puede hacer esto. Una forma sencilla de hacerlo es configurarlo para que detecte solo los archivos de inclusión no utilizados e ignore todos los demás problemas. Esto es bastante sencillo: para habilitar solo el mensaje 766 ("Archivo de encabezado no usado en el módulo"), solo incluya las opciones -w0 + e766 en la línea de comando.

El mismo enfoque también se puede usar con mensajes relacionados como 964 ("Archivo de encabezado no usado directamente en el módulo") y 966 ("Archivo de encabezado incluido indirectamente no usado en el módulo").

FWIW Escribí sobre esto con más detalle en una publicación de blog la semana pasada en http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318 .


fuente
2

Si está buscando eliminar #includearchivos innecesarios para disminuir los tiempos de compilación, es mejor invertir su tiempo y dinero en paralelizar su proceso de compilación usando cl.exe / MP , make -j , Xoreax IncrediBuild , distcc / icecream , etc.

Por supuesto, si ya tiene un proceso de compilación paralelo y todavía está tratando de acelerarlo, entonces limpie sus #includedirectivas y elimine esas dependencias innecesarias.

bk1e
fuente
2

Comience con cada archivo de inclusión y asegúrese de que cada archivo de inclusión solo incluya lo necesario para compilarse. Cualquier archivo de inclusión que falte para los archivos de C ++ se puede agregar a los archivos de C ++.

Para cada archivo de inclusión y fuente, comente cada archivo de inclusión de uno en uno y vea si se compila.

También es una buena idea ordenar los archivos de inclusión alfabéticamente y, cuando esto no sea posible, agregar un comentario.

selwyn
fuente
2
No estoy seguro de cuán práctico sea este comentario, si se trata de una gran cantidad de archivos de implementación.
Sonny
1

Agregar una o ambas de las siguientes #defines excluirá los archivos de encabezado a menudo innecesarios y puede mejorar sustancialmente los tiempos de compilación, especialmente si el código no utiliza las funciones de la API de Windows.

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

Consulte http://support.microsoft.com/kb/166474

Roger Nelson
fuente
1
No se necesitan ambos - VC_EXTRALEAN define WIN32_LEAN_AND_MEAN
Aidan Ryan
1

Si aún no lo ha hecho, usar un encabezado precompilado para incluir todo lo que no va a cambiar (encabezados de plataforma, encabezados de SDK externos o partes estáticas ya completadas de su proyecto) hará una gran diferencia en los tiempos de compilación.

http://msdn.microsoft.com/en-us/library/szfdksca(VS.71).aspx

Además, aunque puede ser demasiado tarde para su proyecto, organizar su proyecto en secciones y no agrupar todos los encabezados locales en un encabezado principal grande es una buena práctica, aunque requiere un poco de trabajo adicional.

anon6439
fuente
Gran explicación de los encabezados precompilados: cygnus-software.com/papers/precompiledheaders.html (No estoy seguro si la generación automática de encabezados precompilados está rota en versiones recientes de VisualStudio, pero vale la pena verificarlo).
idbrii
1

Si trabajara con Eclipse CDT, podría probar http://includator.com para optimizar su estructura de inclusión. Sin embargo, es posible que Includator no sepa lo suficiente sobre las inclusiones predefinidas de VC ++ y la configuración de CDT para usar VC ++ con las inclusiones correctas aún no está integrada en CDT.

PeterSom
fuente
1

El IDE de Jetbrains más reciente, CLion, muestra automáticamente (en gris) las inclusiones que no se utilizan en el archivo actual.

También es posible tener la lista de todas las inclusiones no utilizadas (y también funciones, métodos, etc.) del IDE.

Jean-Michaël Celerier
fuente
0

Algunas de las respuestas existentes afirman que es difícil. De hecho, eso es cierto, porque necesita un compilador completo para detectar los casos en los que una declaración directa sería apropiada. No puede analizar C ++ sin saber qué significan los símbolos; la gramática es demasiado ambigua para eso. Debe saber si un nombre determinado nombra una clase (podría declararse hacia adelante) o una variable (no puede). Además, debe tener en cuenta el espacio de nombres.

MSalters
fuente
Podrías simplemente decir "Decidir qué #incluye son necesarios es equivalente a resolver el problema de la detención. Buena suerte :)" Por supuesto, puedes usar heurística, pero no conozco ningún software libre que haga esto.
Porges
0

Si hay un encabezado en particular que cree que ya no es necesario (por ejemplo, string.h), puede comentar esa inclusión y luego poner esto debajo de todas las inclusiones:

#ifdef _STRING_H_
#  error string.h is included indirectly
#endif

Por supuesto, los encabezados de su interfaz pueden usar una convención #define diferente para registrar su inclusión en la memoria CPP. O sin convención, en cuyo caso este enfoque no funcionará.

Luego reconstruir. Hay tres posibilidades:

  • Se construye bien. string.h no era crítico para la compilación, y su inclusión se puede eliminar.

  • Los viajes del #error. string.g se incluyó indirectamente de alguna manera. Aún no sabe si se requiere string.h. Si es necesario, debe #incluirlo directamente (ver más abajo).

  • Obtiene algún otro error de compilación. string.h era necesario y no se incluye indirectamente, por lo que la inclusión era correcta para empezar.

Tenga en cuenta que dependiendo de la inclusión indirecta cuando su .ho .c usa directamente otro .h es casi seguro que es un error: de hecho, está prometiendo que su código solo requerirá ese encabezado siempre que otro encabezado que esté usando lo requiera, que probablemente no es lo que quisiste decir.

Las advertencias mencionadas en otras respuestas sobre los encabezados que modifican el comportamiento en lugar de declarar cosas que causan fallas de compilación también se aplican aquí.

Britton Kerin
fuente