web-dev-qa-db-fra.com

Comment détecter les fichiers #include inutiles dans un grand projet C ++?

Je travaille sur un grand projet C++ dans Visual Studio 2008 et il y a beaucoup de fichiers avec _ #include directives. Parfois l #includes ne sont que des artefacts et tout se compilera très bien avec leur suppression, et dans d'autres cas, les classes pourraient être déclarées et le #include pourrait être déplacé vers le .cpp fichier. Existe-t-il de bons outils pour détecter ces deux cas?

93
shambolic

Bien qu'il ne révèle pas les fichiers d'inclusion inutiles, Visual studio a un paramètre /showIncludes (clic droit sur un .cpp fichier, Properties->C/C++->Advanced) qui affichera une arborescence de tous les fichiers inclus au moment de la compilation. Cela peut aider à identifier les fichiers qui ne devraient pas être inclus.

Vous pouvez également jeter un coup d'œil à l'idiome pimpl pour vous permettre de vous éloigner de moins de dépendances de fichier d'en-tête pour faciliter la visualisation de la corruption que vous pouvez supprimer.

47
Eclipse

PC Lint fonctionne très bien pour cela, et il trouve également toutes sortes d'autres problèmes loufoques. Il a des options de ligne de commande qui peuvent être utilisées pour créer des outils externes dans Visual Studio, mais j'ai trouvé que le complément Visual Lint est plus facile à utiliser. Même la version gratuite de Visual Lint aide. Mais essayez PC-Lint. Le configurer pour qu'il ne vous donne pas trop d'avertissements prend un peu de temps, mais vous serez étonné de voir ce qu'il se passe.

29
Joe

Il y a un nouvel outil basé sur Clang, include-what-you-use , qui vise à le faire.

28
Josh Kelley

!!AVERTISSEMENT!! Je travaille sur un outil d'analyse statique commercial (pas PC Lint). !!AVERTISSEMENT!!

Il existe plusieurs problèmes avec une approche simple sans analyse:

1) Ensembles de surcharge:

Il est possible qu'une fonction surchargée ait des déclarations provenant de différents fichiers. Il se peut que la suppression d'un fichier d'en-tête entraîne le choix d'une surcharge différente plutôt qu'une erreur de compilation! Le résultat sera un changement silencieux de sémantique qui peut être très difficile à retrouver par la suite.

2) Spécialisations du modèle:

Semblable à l'exemple de surcharge, si vous avez des spécialisations partielles ou explicites pour un modèle, vous voulez qu'elles soient toutes visibles lorsque le modèle est utilisé. Il se peut que les spécialisations du modèle principal se trouvent dans différents fichiers d'en-tête. La suppression de l'en-tête avec la spécialisation ne provoquera pas d'erreur de compilation, mais peut entraîner un comportement indéfini si cette spécialisation avait été sélectionnée. (Voir: Visibilité de la spécialisation de modèle de fonction C++ )

Comme l'a souligné "msalters", effectuer une analyse complète du code permet également d'analyser l'utilisation des classes. En vérifiant comment une classe est utilisée à travers un chemin spécifique de fichiers, il est possible que la définition de la classe (et donc toutes ses dépendances) puisse être supprimée complètement ou au moins déplacée à un niveau plus proche de la source principale dans l'include arbre.

26
Richard Corden

Je ne connais aucun de ces outils, et j'ai pensé à en écrire un dans le passé, mais il s'avère que c'est un problème difficile à résoudre.

Supposons que votre fichier source inclut a.h et b.h; a.h contient #define USE_FEATURE_X et b.h utilise #ifdef USE_FEATURE_X. Si #include "a.h" est mis en commentaire, votre fichier peut toujours être compilé, mais peut ne pas faire ce que vous attendez. Détecter cela par programme n'est pas trivial.

Quel que soit l'outil utilisé, cela devrait également connaître votre environnement de construction. Si a.h ressemble à:

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

Alors USE_FEATURE_X n'est défini que si WINNT est défini, donc l'outil aurait besoin de savoir quelles directives sont générées par le compilateur lui-même ainsi que celles qui sont spécifiées dans la commande de compilation plutôt que dans un fichier d'en-tête.

10
Graeme Perrow

Comme Timmermans, je ne connais aucun outil pour cela. Mais j'ai connu des programmeurs qui ont écrit un script Perl (ou Python) pour essayer de commenter chaque ligne d'inclusion une à la fois, puis de compiler chaque fichier.


Il semble que maintenant Eric Raymond a un outil pour cela .

cpplint.py de Google a une règle "inclure ce que vous utilisez" (parmi beaucoup d'autres), mais pour autant que je sache, non "inclure uniquement ce que vous utilisez. " Néanmoins, cela peut être utile.

9
Max Lybbert

Si vous êtes intéressé par ce sujet en général, vous voudrez peut-être consulter Lakos ' Large Scale C++ Software Design . C'est un peu daté, mais il se heurte à de nombreux problèmes de "conception physique", comme la recherche du minimum absolu d'en-têtes à inclure. Je n'ai pas vraiment vu ce genre de chose discuté ailleurs.

5
Adrian

Essayez Inclure le gestionnaire essayez. Il s'intègre facilement dans Visual Studio et visualise vos chemins d'inclusion qui vous aident à trouver des éléments inutiles. En interne, il utilise Graphviz mais il existe de nombreuses autres fonctionnalités intéressantes. Et bien que ce soit un produit commercial, il a un prix très bas.

4
Alex

Vous pouvez créer un graphique d'inclusion à l'aide de C/C++ Include File Dependencies Watcher , et trouver visuellement les inclusions inutiles.

4
Vladimir

PC-Lint peut en effet le faire. Un moyen simple de le faire est de le configurer pour détecter uniquement les fichiers d'inclusion inutilisés et ignorer tous les autres problèmes. C'est assez simple - pour activer uniquement le message 766 ("Fichier d'en-tête non utilisé dans le module"), il suffit d'inclure les options -w0 + e766 sur la ligne de commande.

La même approche peut également être utilisée avec des messages associés tels que 964 ("Fichier d'en-tête non directement utilisé dans le module") et 966 ("Fichier d'en-tête inclus indirectement non utilisé dans le module").

FWIW J'ai écrit à ce sujet plus en détail dans un article de blog la semaine dernière à http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318 .

3
Anna-Jayne Metcalfe

Si vos fichiers d'en-tête commencent généralement par

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

(au lieu d'utiliser une fois #pragma), vous pouvez changer cela en:

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

Et puisque le compilateur génère le nom du fichier cpp en cours de compilation, cela vous permettra de savoir au moins quel fichier cpp provoque l’en-tête plusieurs fois.

3
Sam

Si vous cherchez à supprimer les inutiles #include fichiers afin de réduire les temps de construction, votre temps et votre argent pourraient être mieux dépensés en parallélisation de votre processus de construction en utilisant cl.exe/MP , make -j , Xoreax IncrediBuild , distcc/ icecream , etc.

Bien sûr, si vous avez déjà un processus de construction parallèle et que vous essayez toujours de l'accélérer, nettoyez votre #include directives et supprimez ces dépendances inutiles.

2
bk1e

Commencez avec chaque fichier include et assurez-vous que chaque fichier include ne comprend que ce qui est nécessaire pour se compiler. Tous les fichiers d'inclusion manquants pour les fichiers C++ peuvent être ajoutés aux fichiers C++ eux-mêmes.

Pour chaque fichier inclus et source, mettez en commentaire chaque fichier inclus un par un et voyez s'il se compile.

C'est aussi une bonne idée de trier les fichiers inclus par ordre alphabétique, et lorsque cela n'est pas possible, ajoutez un commentaire.

2
selwyn

L'ajout d'un ou des deux #defines suivants exclura les fichiers d'en-tête souvent inutiles et peut considérablement améliorer les temps de compilation, surtout si le code n'utilise pas les fonctions de l'API Windows.

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

Voir http://support.Microsoft.com/kb/166474

1
Roger Nelson

Si vous ne l'êtes pas déjà, l'utilisation d'un en-tête précompilé pour inclure tout ce que vous n'allez pas changer (en-têtes de plate-forme, en-têtes SDK externes ou éléments statiques déjà terminés de votre projet) fera une énorme différence dans les temps de construction.

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

De plus, bien qu'il puisse être trop tard pour votre projet, organiser votre projet en sections et ne pas regrouper tous les en-têtes locaux dans un grand en-tête principal est une bonne pratique, même si cela demande un peu de travail supplémentaire.

1
anon6439

Le dernier IDE Jetbrains, CLion, affiche automatiquement (en gris) les inclusions qui ne sont pas utilisées dans le fichier actuel.

Il est également possible d'avoir la liste de toutes les inclusions inutilisées (ainsi que les fonctions, méthodes, etc ...) de l'IDE.

1

Si vous souhaitez travailler avec Eclipse CDT, vous pouvez essayer http://includator.com pour optimiser votre structure d'inclusion. Cependant, Includator peut ne pas en savoir suffisamment sur les inclusions prédéfinies de VC++ et la configuration de CDT pour utiliser VC++ avec des inclusions correctes n'est pas encore intégrée à CDT.

1
PeterSom

Peut-être un peu tard, mais j'ai déjà trouvé un script WebKit Perl qui faisait exactement ce que vous vouliez. Je pense qu'il faudra un peu d'adaptation (je ne connais pas bien Perl), mais ça devrait faire l'affaire:

http://trac.webkit.org/browser/branches/old/safari-3-2-branch/WebKitTools/Scripts/find-extra-includes

(c'est une ancienne branche car le tronc n'a plus le fichier)

0
rubenvb

S'il y a un en-tête particulier qui, selon vous, n'est plus nécessaire (disons string.h), vous pouvez commenter ce qui inclut puis le mettre en dessous de tous les inclus:

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

Bien sûr, vos en-têtes d'interface peuvent utiliser une convention #define différente pour enregistrer leur inclusion dans la mémoire CPP. Ou pas de convention, auquel cas cette approche ne fonctionnera pas.

Reconstruisez ensuite. Il y a trois possibilités:

  • Ça se construit bien. string.h n'était pas critique pour la compilation, et son inclusion peut être supprimée.

  • Les #error voyages. string.g a été inclus indirectement d'une manière ou d'une autre Vous ne savez toujours pas si string.h est requis. Si c'est nécessaire, vous devez directement # l'inclure (voir ci-dessous).

  • Vous obtenez une autre erreur de compilation. string.h était nécessaire et n'est pas inclus indirectement, donc l'inclusion était correcte pour commencer.

Notez que selon l'inclusion indirecte lorsque votre .h ou .c utilise directement un autre .h est presque certainement un bug: vous promettez en effet que votre code ne nécessitera cet en-tête que tant qu'un autre en-tête que vous utilisez l'exige, ce qui n'est probablement pas ce que vous vouliez dire.

Les mises en garde mentionnées dans d'autres réponses sur les en-têtes qui modifient le comportement plutôt que de déclarer des choses qui provoquent des échecs de construction s'appliquent également ici.

0
Britton Kerin

Certaines des réponses existantes indiquent que c'est difficile. C'est en effet vrai, car vous avez besoin d'un compilateur complet pour détecter les cas dans lesquels une déclaration directe serait appropriée. Vous ne pouvez pas analyser C++ sans savoir ce que signifient les symboles; la grammaire est tout simplement trop ambiguë pour cela. Vous devez savoir si un certain nom nomme une classe (pourrait être déclarée en aval) ou une variable (ne peut pas). En outre, vous devez être conscient de l'espace de noms.

0
MSalters