Comment puis-je savoir quelles parties du code ne sont jamais utilisées?

j'ai du code C++ que je suis censé supprimer du code inutilisé. Le problème est que la base de code est grande.

Comment puis-je savoir quel code n'est jamais appelé/jamais utilisé?

301
demandé sur vrwim 2011-01-27 10:59:26
la source

18 ответов

il existe deux variétés de code non utilisé:

  • le local, c'est-à-dire que dans certaines fonctions certains chemins ou variables ne sont pas utilisés (ou utilisés mais sans signification, comme écrit mais jamais lu)
  • la mondiale: les fonctions qui ne sont jamais appelés, des objets globaux qui ne sont jamais consultés

Pour le premier type, un bon compilateur peut aider:

  • -Wunused (GCC, Clang ) devrait mettre en garde contre les variables inutilisées, Clang analyseur non utilisé a même été incrémenté pour mettre en garde contre les variables qui ne sont jamais lus (même si utilisé).
  • -Wunreachable-code (ancien GCC, supprimé en 2010 ) devrait mettre en garde contre les blocs locaux qui ne sont jamais accessibles (cela se produit avec des retours précoces ou des conditions qui évaluent toujours à vrai)
  • il n'y a pas d'option que je connaisse pour mettre en garde contre les non utilisés catch les blocs, car le compilateur ne peut généralement pas prouver qu'aucune exception n'est levée.

Pour le second type, c'est beaucoup plus difficile. Statiquement, il nécessite une analyse de programme complète, et même si l'optimisation de temps de lien peut effectivement supprimer le code mort, dans la pratique, le programme a été tellement transformé au moment où il est effectué qu'il est presque impossible de transmettre des informations significatives à l'utilisateur.

il y a donc deux approches:

  • La théorie est celle de l'utilisation d'un analyseur statique. Un logiciel qui va examiner le code entier en détail et trouver tous les chemins d'écoulement. En pratique, je n'en connais aucun qui pourrait fonctionner ici.
  • le plus pragmatique est d'utiliser un heuristique: utiliser un outil de couverture de code (dans la chaîne GNU c'est gcov ). Notez que des options spécifiques doivent être passées lors de la compilation pour que cela fonctionne correctement). Vous exécutez le code outil de couverture avec un bon ensemble d'entrées variées (vos tests unitaires ou vos tests de non-régression), le code mort est nécessairement à l'intérieur du code non atteint... et donc, vous pouvez commencer à partir d'ici.

si vous êtes extrêmement intéressé par le sujet, et que vous avez le temps et l'envie d'élaborer vous-même un outil, je vous suggérerais d'utiliser les bibliothèques Clang pour construire un tel outil.

  1. utilisez la bibliothèque Clang pour obtenir un AST (résumé arbre de syntaxe)
  2. effectuer une analyse de pointage et de balayage à partir des points d'entrée

parce que Clang va analyser le code pour vous, et effectuer la résolution de surcharge, vous n'aurez pas à traiter avec les règles de langues C++, et vous serez en mesure de se concentrer sur le problème à portée de main.

cependant ce genre de technique ne peut pas identifier les dérogations virtuelles qui ne sont pas utilisées, car elles peuvent être appelées par le code de tiers vous ne peut pas raisonner sur.

192
répondu Matthieu M. 2012-08-07 18:03:04
la source

pour le cas des fonctions entières inutilisées (et des variables globales inutilisées), GCC peut en fait faire la plupart du travail pour vous à condition que vous utilisiez GCC et GNU ld.

lors de la compilation de la source, utilisez -ffunction-sections et -fdata-sections , puis lors de la liaison utiliser -Wl,--gc-sections,--print-gc-sections . Le linker va maintenant lister toutes les fonctions qui ont pu être supprimées parce qu'elles n'ont jamais été appelées et tous les globals qui n'ont jamais été référencés.

(bien sûr, vous pouvez aussi sauter la partie --print-gc-sections et laisser le linker enlever les fonctions silencieusement, mais les garder dans la source.)

Note: ceci ne trouvera que des fonctions complètes inutilisées, il ne fera rien au sujet du code mort dans les fonctions. Les fonctions appelées à partir du code mort dans les fonctions en direct seront également conservées.

certaines fonctionnalités spécifiques au c++poseront également des problèmes, en particulier:

  • virtuel fonction. Sans savoir quelles sous-classes existent et quelles sont réellement instanciées au moment de l'exécution, vous ne pouvez pas savoir quelles fonctions virtuelles vous devez exister dans le programme final. Le linker n'a pas assez d'informations à ce sujet, donc il devra garder tous autour d'eux.
  • Globals avec les constructeurs, et leurs constructeurs. En général, le linker ne peut pas savoir que le constructeur d'un global n'a pas d'effets secondaires, il doit donc le faire fonctionner. Évidemment, cela signifie que l' mondial lui-même doit également être conservé.

dans les deux cas, tout ce qui est utilisé par une fonction virtuelle ou un constructeur de variables globales doit aussi être maintenu.

une mise en garde supplémentaire est que si vous construisez une bibliothèque partagée, les paramètres par défaut dans GCC exportera chaque fonction dans la bibliothèque partagée, faisant en sorte qu'il soit" utilisé " en ce qui concerne le linker. Pour réparer ce dont vous avez besoin pour définir la valeur par défaut à cacher des symboles au lieu d'exporter (en utilisant par exemple -fvisibility=hidden ), puis sélectionner explicitement les fonctions exportées que vous devez exporter.

32
répondu olsner 2011-02-01 04:07:00
la source

si vous utilisez g++, vous pouvez utiliser ce drapeau -Wunused

selon la documentation:

avertir chaque fois qu'une variable n'est pas utilisée à part sa déclaration, chaque fois que une fonction est déclarée statique, mais jamais défini, chaque fois que l'étiquette est déclaré Mais non utilisé, et chaque fois qu'un déclaration calcule un résultat pas explicitement utilisé.

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

Edit : voici un autre drapeau utile -Wunreachable-code Selon la documentation:

cette option est destinée à avertir lorsque le compilateur détecte qu'au moins une ligne entière de code source ne sera jamais exécutée, parce qu'une condition n'est jamais satisfaite ou parce qu'elle est après une procédure qui ne retourne jamais.

mise à Jour : j'ai trouvé des sujets similaires détection du code Mort en héritage en C/C++ projet

25
répondu UmmaGumma 2017-05-23 15:02:42
la source

je pense que vous recherchez un couverture de code outil. Un outil de couverture de code analysera votre code tel qu'il est en cours d'exécution, et vous indiquera quelles lignes de code ont été exécutées et combien de fois, ainsi que celles qui ne l'ont pas été.

vous pouvez essayer de donner une chance à cet outil de couverture de code source ouvert: TestCocoon - outil de couverture de code pour C/C++ et C#.

18
répondu Carlos V 2011-05-23 14:10:26
la source

la vraie réponse ici est: vous ne pouvez jamais vraiment savoir avec certitude.

au moins, pour les cas non triviaux, vous ne pouvez pas être sûr que vous avez tout. Considérez ce qui suit de article de Wikipedia sur le code inaccessible :

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

comme Wikipedia le note correctement, un compilateur intelligent peut être capable d'attraper quelque chose comme ça. Mais envisager une modification:

int y;
cin >> y;
double x = sqrt((double)y);

if (x != 0 && x < 1)
{
  doStuff();
}

le compilateur attraper? Peut-être. Mais pour ce faire, il devra faire plus qu'exécuter sqrt contre une valeur scalaire constante. Il devra comprendre que (double)y sera toujours un entier (facile), puis de comprendre les mathématiques de la gamme de sqrt pour l'ensemble des entiers (dur). Un compilateur très sophistiqué pourrait être capable de faire cela pour la fonction sqrt , ou pour chaque fonction dans math.h , ou pour toute fonction à entrée fixe dont il peut trouver le domaine. Cela devient très, très complexe, et cette complexité est essentiellement illimitée. Vous pouvez continuer à ajouter des couches de sophistication à votre compilateur, mais il y aura toujours un moyen de se faufiler dans un code qui sera inaccessible pour un ensemble donné d'intrants.

et puis il y a les ensembles d'entrée qui simplement ne sont jamais entrés. "1519180920 d'Entrée" cela n'aurait aucun sens dans la vie réelle, ou bloqué par une logique de validation ailleurs. Il n'y a pas une façon pour le compilateur de les connaître.

le résultat final de ceci est que bien que les outils logiciels que d'autres ont mentionnés soient extrêmement utiles, vous n'allez jamais savoir avec certitude que vous avez tout attrapé à moins que vous ne passiez par le code manuellement après. Même à ce moment-là, vous ne serez jamais certain de n'avoir rien manqué.

la seule vraie solution, IMHO, est d'être aussi vigilant que possible, utiliser l'automatisation à votre disposition, refactor où vous pouvez, et de chercher constamment des moyens d'améliorer votre code. Bien sûr, c'est une bonne idée de le faire de toute façon.

15
répondu Justin Morgan 2012-09-17 19:01:36
la source

Je ne l'ai pas utilisé moi-même, mais cppcheck , prétend trouver des fonctions inutilisées. Cela ne résoudra probablement pas tout le problème, mais cela pourrait être un début.

12
répondu Mr Shark 2011-01-27 13:00:33
la source

vous pouvez essayer d'utiliser PC-lint/FlexeLint du logiciel Gimple . Il prétend à

trouver des macros inutilisées, typedef's, classes, membres, déclarations, etc. dans l'ensemble du projet

je l'ai utilisé pour l'analyse statique et l'a trouvé très bon, mais je dois admettre que je n'ai pas utilisé pour trouver du code mort.

9
répondu Tony 2011-01-27 13:08:25
la source

mon approche normale pour trouver des trucs inutilisés est

  1. assurez-vous que le système de construction gère correctement le suivi des dépendances
  2. installe un second moniteur, avec une fenêtre de terminal plein écran, exécute des compilations répétées et affiche la première screenful de sortie. watch "make 2>&1" a tendance à faire l'affaire sur Unix.
  3. lancer une opération find-and-replace sur toute l'arborescence des sources, en ajoutant "//? "au début de chaque ligne
  4. fixer la première erreur signalée par le compilateur, par la suppression de "//?"dans les lignes correspondantes.
  5. répéter jusqu'à ce qu'il n'y ait plus d'erreurs.

il s'agit d'un processus assez long, mais qui donne de bons résultats.

4
répondu Simon Richter 2011-01-27 15:56:07
la source

marquer autant de fonctions et variables publiques que privées ou protégées sans causer d'erreur de compilation, tout en faisant cela, essayer également de reformuler le code. En rendant les fonctions privées et dans une certaine mesure protégées, vous avez réduit votre zone de recherche puisque les fonctions privées ne peuvent être appelées que de la même classe (à moins qu'il n'y ait des macros stupides ou d'autres trucs pour contourner la restriction d'accès, et si c'est le cas, je vous recommande de trouver un nouvel emploi). Il est beaucoup plus facile à déterminer que vous vous n'avez pas besoin d'une fonction privée puisque seule la classe sur laquelle vous travaillez actuellement peut appeler cette fonction. Cette méthode est plus facile si votre base de code a de petites classes et est vaguement couplé. Si votre base de code n'a pas de petites classes ou n'a pas de couplage très serré, je suggère de les nettoyer d'abord.

ensuite sera de marquer toutes les fonctions publiques restantes et de faire un graphique d'appel pour comprendre la relation entre les classes. De cet arbre, essayez de comprendre quelle partie de la direction générale ressemble, il peut être coupé.

l'avantage de cette méthode est que vous pouvez le faire sur la base de chaque module, il est donc facile de continuer à passer votre unittest sans avoir une grande période de temps lorsque vous avez une base de code brisée.

4
répondu Lie Ryan 2011-01-27 18:38:33
la source

si vous êtes sous Linux, vous pouvez consulter callgrind , un outil d'analyse de programme C/C++ qui fait partie de la suite valgrind , qui contient également des outils qui vérifient les fuites de mémoire et autres erreurs de mémoire (que vous devriez également utiliser). Il analyse d'une instance en cours d'exécution de votre programme, et produit des données sur son graphe d'appel, et sur les coûts de l'exécution des nœuds du graphe d'appel. Il est généralement utilisé pour l'analyse des performances, mais il produit également un graphique d'appel pour vos applications, de sorte que vous pouvez voir quelles fonctions sont appelées, ainsi que leurs appelants.

c'est évidemment complémentaire aux méthodes statiques mentionnées ailleurs sur la page, et cela ne sera utile que pour éliminer les classes, méthodes et fonctions totalement inutilisées - cela n'aidera pas à trouver du code mort à l'intérieur des méthodes qui sont effectivement appelées.

3
répondu Adam Higuera 2011-02-01 01:15:56
la source

Je n'ai vraiment pas utilisé un outil qui fait une telle chose... Mais, autant que j'ai vu dans toutes les réponses, personne n'a jamais dit que ce problème est inimaginable.

Qu'est-ce que je veux dire par là? Que ce problème ne peut être résolu par un algorithme jamais sur un ordinateur. Ce théorème (qu'un tel algorithme n'existe pas) est un corollaire du problème D'arrêt de Turing.

tous les outils que vous utiliserez ne sont pas des algorithmes mais des heuristiques (I. e non exact algorithme.) Ils ne vous donneront pas exactement tout le code qui n'est pas utilisé.

3
répondu geekazoid 2011-04-18 20:43:53
la source

une façon est d'utiliser un débogueur et la fonctionnalité de compilateur d'éliminer le code machine non utilisé pendant la compilation.

une fois qu'un code machine est éliminé le débogueur ne vous laissera pas mettre un breakpojnt sur la ligne correspondante du code source. Donc vous mettez des points de rupture partout et démarrez le programme et inspectez les points de rupture - ceux qui sont dans l'état" no code loaded for this source " correspondent au code éliminé - soit ce code n'est jamais appelé ou il a été inlined et vous devez faire une analyse minimale pour trouver lequel de ces deux est arrivé.

au moins c'est comme ça que ça marche dans Visual Studio et je suppose que d'autres outils peuvent aussi le faire.

c'est beaucoup de travail, mais je suppose plus rapide que l'analyse manuelle de tous les codes.

2
répondu sharptooth 2011-01-27 11:22:33
la source

cela dépend de la plate-forme que vous utilisez pour créer votre application.

par exemple, si vous utilisez Visual Studio, vous pouvez utiliser un outil comme .net Ants Profiler qui est capable de Analyser et de profiler votre code. De cette façon, vous devriez rapidement savoir quelle partie de votre code est réellement utilisée. Eclipse a aussi des plugins équivalents.

Sinon, si vous avez besoin de savoir quelle fonction de votre application est réellement utilisée par votre fin l'utilisateur, et si vous pouvez vous libérer de votre application facilement, vous pouvez utiliser un fichier journal pour une vérification.

pour chaque fonction principale, vous pouvez tracer son utilisation, et après quelques jours/semaine juste obtenir ce fichier journal, et y jeter un oeil.

1
répondu AUS 2011-05-23 14:07:40
la source

CppDepend est un outil commercial qui peut détecter les types, les méthodes et les champs inutilisés, et faire beaucoup plus. Il est disponible pour Windows et Linux (mais n'a actuellement pas de support 64 bits), et est livré avec un essai de 2 semaines.

Avertissement: je ne travaille pas là, mais je possède une licence de cet outil (ainsi que NDepend , qui est une alternative plus puissante pour .NET code).

pour les curieux, voici un exemple de règle intégrée (personnalisable) pour la détection des méthodes mortes, écrite dans 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] }
1
répondu Roman Boiko 2012-11-05 04:00:44
la source

Je ne pense pas que cela puisse être fait automatiquement.

même avec les outils de couverture de code, vous devez fournir suffisamment de données d'entrée pour exécuter.

peut être un outil d'analyse statique très complexe et à prix élevé tel que Coverity's ou compilateur LLVM pourrait être utile.

mais je ne suis pas sûr et je préférerais une révision manuelle du code.

UPDATED

bien.. enlever seulement les variables inutilisées, les fonctions inutilisées n'est pas difficile cependant.

mise à JOUR

après avoir lu d'autres réponses et commentaires, je suis plus convaincu que ce n'est pas possible.

vous devez connaître le code pour avoir une mesure de couverture de code significative, et si vous savez que beaucoup d'édition manuelle sera plus rapide que préparer/exécuter/réviser les résultats de couverture.

0
répondu 9dan 2011-01-27 11:55:59
la source

j'ai eu un ami qui m'a posé cette question aujourd'hui même, et j'ai regardé autour de quelques développements Clang prometteurs, par exemple ASTMatcher s et le analyseur statique qui pourraient avoir une visibilité suffisante dans la marche-dessus pendant la compilation pour déterminer les sections de code mortes, mais alors j'ai trouvé ceci:

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

c'est à peu près une description complète de la façon d'utiliser quelques drapeaux GCC qui sont apparemment conçus dans le but d'identifier des symboles non référencés!

0
répondu Steven Lu 2013-07-11 07:43:00
la source

le problème général de si une fonction sera appelée est NP-Complete. Vous ne pouvez pas savoir à l'avance de manière générale si une fonction sera appelée car vous ne saurez pas si une machine de Turing s'arrêtera un jour. Vous pouvez obtenir s'il y a un chemin (statique) qui va de main() à la fonction que vous avez écrite, mais cela ne vous justifie pas qu'il soit appelé un jour.

0
répondu Luis Colorado 2014-09-12 08:22:37
la source

bien si vous utilisez g++ vous pouvez utiliser ce drapeau-Wunused

selon la documentation:

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

Edit: voici un autre drapeau utile-Wunreachable-code selon la documentation:

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.
-3
répondu ram singh 2011-01-27 13:51:41
la source

Autres questions sur c++ optimization dead-code