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

96

Je travaille sur un gros projet C ++ dans Visual Studio 2008, et il y a beaucoup de fichiers avec des #includedirectives inutiles . Parfois, les #includes ne sont que des artefacts et tout se compilera correctement s'ils sont supprimés, et dans d'autres cas, les classes peuvent être déclarées en avant et le #include peut être déplacé vers le .cppfichier. Existe-t-il de bons outils pour détecter ces deux cas?

shambolique
la source

Réponses:

50

Bien qu'il ne révèle pas les fichiers d'inclusion inutiles, Visual Studio dispose d'un paramètre /showIncludes(clic droit sur un .cppfichier 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 avoir besoin d'être inclus.

Vous pouvez également jeter un oeil à l'idiome pimpl pour vous permettre de vous en sortir avec moins de dépendances de fichiers d'en-tête pour vous permettre de voir plus facilement la cruauté que vous pouvez supprimer.

Éclipse
la source
1
/ showincludes est génial. Faire cela manuellement était intimidant sans cela.
shambolic le
30

PC Lint fonctionne très bien pour cela, et il trouve pour vous toutes sortes d'autres problèmes loufoques aussi. 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 que cela donne.

Joe
la source
3
Vous trouverez des instructions sur la façon de procéder avec pc-lint sur riverblade.co.uk
David Sykes
26

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

Il y a 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 à retracer par la suite.

2) Spécialisations de modèles:

Similaire à 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 pour le modèle principal se trouvent dans des fichiers d'en-tête différents. La suppression de l'en-tête avec la spécialisation ne provoquera pas d'erreur de compilation, mais peut entraîner un comportement non défini si cette spécialisation avait été sélectionnée. (Voir: Visibilité de la spécialisation des modèles de la fonction C ++ )

Comme indiqué par «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 via un chemin spécifique de fichiers, il est possible que la définition de la classe (et donc de 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'inclusion arbre.

Richard Corden
la source
@RichardCorden: Votre logiciel (QA C ++) est trop cher.
Xander Tulip
13
@XanderTulip: Il est difficile de répondre à cela sans se retrouver dans un argumentaire de vente - alors je m'excuse à l'avance. À mon humble avis, ce que vous devez considérer, c'est combien de temps il faudrait à un bon ingénieur pour trouver des choses comme celle-ci (ainsi que de nombreux autres bogues de langage / flux de contrôle) dans tout projet de taille raisonnable. Au fur et à mesure que le logiciel change, la même tâche doit être répétée encore et encore. Ainsi, lorsque vous calculez le temps gagné, le coût de l'outil n'est probablement pas significatif.
Richard Corden
10

Je ne connais pas de tels 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 comprend ah et bh; ah contient #define USE_FEATURE_Xet bh 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é, il faudrait également connaître votre environnement de construction. Si ah ressemble à:

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

Alors USE_FEATURE_Xn'est défini que si WINNTest défini, de sorte que 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.

Graeme Perrow
la source
9

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 compiler chaque fichier.


Il semble que maintenant Eric Raymond dispose d'un outil pour cela .

Le fichier 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". Même ainsi, cela peut être utile.

Max Lybbert
la source
J'ai dû rire quand j'ai lu celui-ci. Mon patron a fait cela précisément sur l'un de nos projets le mois dernier. L'en-tête réduit comprend plusieurs facteurs.
Don Wakefield
2
codewarrior sur le mac avait l'habitude d'avoir un script intégré pour faire cela, commenter, compiler, en cas d'erreur un-comment, continuer jusqu'à la fin du #includes. Cela ne fonctionnait que pour #includes en haut d'un fichier, mais c'est généralement là qu'ils se trouvent. Ce n'est pas parfait, mais cela garde les choses raisonnablement saines.
slycrel
5

Si vous êtes intéressé par ce sujet en général, vous voudrez peut-être consulter la conception de logiciels C ++ à grande échelle de Lakos . Il est un peu daté, mais aborde de nombreux problèmes de "conception physique", comme trouver le minimum absolu d'en-têtes à inclure. Je n'ai pas vraiment vu ce genre de chose discuté ailleurs.

Adrian
la source
4

Essayez Inclure Manager . Il s'intègre facilement dans Visual Studio et visualise vos chemins d'inclusion, ce qui vous aide à trouver des éléments inutiles. En interne, il utilise Graphviz mais il existe de nombreuses autres fonctionnalités intéressantes. Et bien qu'il s'agisse d'un produit commercial, son prix est très bas.

Alex
la source
3

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 #pragma une fois), vous pouvez le changer en:

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

Et puisque le compilateur affiche le nom du fichier cpp en cours de compilation, cela vous permettrait de savoir au moins quel fichier cpp provoque l'ajout de l'en-tête plusieurs fois.

Sam
la source
12
Je pense que c'est bien d'inclure des en-têtes plusieurs fois. Il est bon d'inclure ce que vous utilisez et de ne pas dépendre de vos fichiers d'inclusion pour le faire. Je pense que ce que le PO veut, c'est trouver des #includes qui ne sont pas réellement utilisés.
Ryan Ginstrom
12
IMO activement mauvaise chose à faire. Les en-têtes doivent inclure d'autres en-têtes lorsqu'ils ne fonctionneraient pas sans eux. Et quand vous avez A.het B.hque les deux dépendez C.het que vous incluez A.het B.h, parce que vous avez besoin à la fois, vous allez inclure C.hdeux fois, mais c'est très bien, parce que le compilateur sauter la deuxième fois et si vous ne l' avez pas, vous auriez à retenir à toujours inclure C.havant A.hou B.hse retrouver dans des inclusions beaucoup plus inutiles.
Jan Hudec
5
Le contenu est précis, c'est une bonne solution pour trouver des en-têtes qui sont inclus plusieurs fois. Cependant, cela ne répond pas à la question initiale et je ne peux pas imaginer quand ce serait une bonne idée. Les fichiers Cpp doivent inclure tous les en-têtes dont ils dépendent, même si l'en-tête est inclus avant ailleurs. Vous ne voulez pas que votre projet soit spécifique à l'ordre de compilation ou supposer qu'un en-tête différent inclura celui dont vous avez besoin.
jaypb
3

PC-Lint peut effectivement 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"), incluez simplement 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 indirectement inclus 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 .


la source
2

Si vous cherchez à supprimer des #includefichiers inutiles afin de réduire les temps de construction, votre temps et votre argent pourraient être mieux dépensés à paralléliser votre processus de construction à l'aide de 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, alors nettoyez vos #includedirectives et supprimez ces dépendances inutiles.

bk1e
la source
2

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

Pour chaque fichier d'inclusion et source, commentez chaque fichier d'inclusion un par un et voyez s'il se compile.

Il est également judicieux de trier les fichiers d'inclusion par ordre alphabétique, et lorsque ce n'est pas possible, ajoutez un commentaire.

Selwyn
la source
2
Je ne sais pas à quel point ce commentaire est pratique, si un très grand nombre de fichiers d'implémentation sont impliqués.
Sonny
1

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, en particulier si le code n'utilise pas les fonctions API Windows.

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

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

Roger Nelson
la source
1
Pas besoin des deux - VC_EXTRALEAN définit WIN32_LEAN_AND_MEAN
Aidan Ryan
1

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 modifier (en-têtes de plate-forme, en-têtes de SDK externes ou parties statiques déjà terminées 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 soit peut-être trop tard pour votre projet, organiser votre projet en sections et ne pas regrouper tous les en-têtes locaux dans un seul grand en-tête principal est une bonne pratique, même si cela demande un peu de travail supplémentaire.

anon6439
la source
Excellente explication des en-têtes précompilés: cygnus-software.com/papers/precompiledheaders.html (Je ne sais pas si la génération automatique des en-têtes précompilés est cassée dans les versions récentes de VisualStudio, mais cela vaut la peine de vérifier.)
idbrii
1

Si vous travaillez 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.

PeterSom
la source
1

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 tous les includes inutilisés (ainsi que les fonctions, méthodes, etc ...) de l'EDI.

Jean-Michaël Celerier
la source
0

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 simplement trop ambiguë pour cela. Vous devez savoir si un certain nom nomme une classe (peut être déclarée en avant) ou une variable (impossible). En outre, vous devez être conscient de l'espace de noms.

MSalters
la source
Vous pourriez simplement dire: "Décider quels #includes sont nécessaires équivaut à résoudre le problème de l'arrêt. Bonne chance :)" Bien sûr, vous pouvez utiliser l'heuristique, mais je ne connais aucun logiciel libre qui fasse cela.
porges le
0

S'il y a un en-tête particulier qui ne vous semble plus nécessaire (par exemple string.h), vous pouvez commenter cet include, puis le mettre sous tous les includes:

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

Bien sûr, les en-têtes de votre 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.

Puis reconstruisez. Il existe trois possibilités:

  • Il construit bien. string.h n'était pas critique pour la compilation, et l'inclusion correspondante peut être supprimée.

  • Les voyages #error. string.g a été inclus indirectement. Vous ne savez toujours pas si string.h est requis. S'il 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 au départ.

Notez que dépendre de l'inclusion indirecte lorsque votre .h ou .c utilise directement un autre .h est presque certainement un bogue: 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 le requiert, ce 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 la déclaration de choses qui provoquent des échecs de construction s'appliquent également ici.

Britton Kerin
la source