Wie kann ich wissen, welche Teile im Code niemals verwendet werden?

312

Ich habe alten C ++ - Code, aus dem ich nicht verwendeten Code entfernen soll. Das Problem ist, dass die Codebasis groß ist.

Wie kann ich herausfinden, welcher Code niemals aufgerufen / nie benutzt wird?

user63898
quelle
4
Ich denke, eine Code-Abfragesprache gibt Ihnen einen besseren Überblick über Ihr Projekt als Ganzes. Ich bin mir nicht sicher über die C ++ - Welt, aber es scheint cppdepend.com zu geben (dies ist nicht kostenlos), das anständig genug aussieht. Möglicherweise ist so etwas kostenlos erhältlich. Die andere Sache ist, bevor Sie irgendeine Art von Refactoring durchführen, wäre es vernünftig, Unit-Tests durchzuführen, wenn Sie dies noch nicht tun. Bei Unit-Tests können Sie Ihren Code von Ihren Tools zur Codeabdeckung profilieren lassen, was selbst dazu beiträgt, den toten Code zu entfernen, wenn Sie diesen Code nicht abdecken können.
Biswanath
3
Überprüfen Sie die Referenz hier: en.wikipedia.org/wiki/Unreachable_code
Martin York
6
Ich finde ein ähnliches Thema. stackoverflow.com/questions/229069/…
UmmaGumma
3
Ja, eines der lustigen Dinge in C ++ ist, dass das Entfernen "nicht verwendeter" Funktionen das Ergebnis eines Programms immer noch verändern kann.
MSalters
1
@MSalters: Das ist interessant ... Damit dies der Fall ist, müssten wir darüber sprechen, welche Funktion in einem Überlastungssatz für einen bestimmten Aufruf ausgewählt wird, richtig? Meines Wissens ist es nicht möglich, diesen Aufruf in die 2. aufzulösen , wenn zwei Funktionen benannt f()sind und ein Aufruf zum f()eindeutigen Auflösen in die erste Funktion nur durch Hinzufügen einer dritten Funktion mit dem Namen f()"das" Schlimmste, was Sie tun können "Durch Hinzufügen dieser dritten Funktion wird der Aufruf mehrdeutig und verhindert, dass das Programm kompiliert wird. Würde gerne (= entsetzt sein) ein Gegenbeispiel sehen.
j_random_hacker

Antworten:

197

Es gibt zwei Arten von nicht verwendetem Code:

  • die lokale, dh in einigen Funktionen werden einige Pfade oder Variablen nicht verwendet (oder verwendet, aber auf keine sinnvolle Weise, wie geschrieben, aber nie gelesen)
  • die globale: Funktionen, die niemals aufgerufen werden, globale Objekte, auf die niemals zugegriffen wird

Für die erste Art kann ein guter Compiler helfen:

  • -Wunused(GCC, Clang ) sollte vor nicht verwendeten Variablen warnen. Der nicht verwendete Clang-Analysator wurde sogar erhöht, um vor Variablen zu warnen, die niemals gelesen werden (obwohl sie verwendet werden).
  • -Wunreachable-code(älteres GCC, 2010 entfernt ) sollte vor lokalen Blöcken warnen, auf die nie zugegriffen wird (dies geschieht bei frühen Rückgaben oder Bedingungen, die immer als wahr ausgewertet werden).
  • Es gibt keine mir bekannte Option, um vor nicht verwendeten catchBlöcken zu warnen , da der Compiler im Allgemeinen nicht beweisen kann, dass keine Ausnahme ausgelöst wird.

Für die zweite Art ist es viel schwieriger. Statisch erfordert es eine Analyse des gesamten Programms, und obwohl die Optimierung der Verbindungszeit tatsächlich toten Code entfernen kann, wurde das Programm in der Praxis zum Zeitpunkt seiner Ausführung so stark transformiert, dass es nahezu unmöglich ist, dem Benutzer aussagekräftige Informationen zu übermitteln.

Es gibt daher zwei Ansätze:

  • Die theoretische besteht darin, einen statischen Analysator zu verwenden. Eine Software, die den gesamten Code auf einmal detailliert untersucht und alle Flusspfade findet. In der Praxis kenne ich keine, die hier funktionieren würden.
  • Das pragmatische ist die Verwendung einer Heuristik: Verwenden Sie ein Tool zur Codeabdeckung (in der GNU-Kette ist dies gcov. Beachten Sie, dass bestimmte Flags während der Kompilierung übergeben werden sollten, damit es ordnungsgemäß funktioniert). Sie führen das Tool zur Codeabdeckung mit einer Reihe unterschiedlicher Eingaben aus (Ihre Unit-Tests oder Nicht-Regressionstests). Der tote Code befindet sich notwendigerweise im nicht erreichten Code ... und Sie können von hier aus beginnen.

Wenn Sie sich sehr für das Thema interessieren und die Zeit und die Neigung haben, ein Werkzeug selbst zu erarbeiten, würde ich empfehlen, die Clang-Bibliotheken zu verwenden, um ein solches Werkzeug zu erstellen.

  1. Verwenden Sie die Clang-Bibliothek, um einen AST (Abstract Syntax Tree) zu erhalten.
  2. Führen Sie ab den Einstiegspunkten eine Mark-and-Sweep-Analyse durch

Da Clang den Code für Sie analysiert und eine Überlastungsauflösung durchführt, müssen Sie sich nicht mit den Regeln für C ++ - Sprachen befassen und können sich auf das jeweilige Problem konzentrieren.

Diese Art von Technik kann jedoch die nicht verwendeten virtuellen Überschreibungen nicht identifizieren, da sie von Code von Drittanbietern aufgerufen werden können, über den Sie nicht nachdenken können.

Matthieu M.
quelle
7
Sehr schön, +1. Ich finde es gut, dass Sie zwischen Code unterscheiden, der statisch so festgelegt werden kann, dass er unter keinen Umständen ausgeführt wird, und Code, der nicht in einem bestimmten Lauf ausgeführt wird, aber möglicherweise ausgeführt werden kann. Ersteres ist meiner Meinung nach wichtig, und wie Sie sagen, ist eine Erreichbarkeitsanalyse mit dem AST des gesamten Programms der Weg, um es zu erhalten. (Zu verhindern, foo()dass es als "aufgerufen" markiert wird, wenn es nur in erscheint, if (0) { foo(); }wäre ein Bonus, erfordert aber zusätzliche Intelligenz.)
j_random_hacker
@j_random_hacker: Vielleicht wäre die Verwendung des CFG (Control-Flow Graph) jetzt besser, wenn ich darüber nachdenke (dank Ihres Beispiels). Ich weiß, dass Clang daran interessiert ist, über tautologische Vergleiche wie den von Ihnen erwähnten zu sprechen, und daher könnten wir mithilfe der CFG möglicherweise frühzeitig toten Code erkennen.
Matthieu M.
@Matthieu: Ja, vielleicht meine ich auch CFG anstelle von AST :) Was ich meine ist: ein Digraph, in dem die Eckpunkte Funktionen sind und es eine Kante von Funktion x zu Funktion y gibt, wann immer x möglicherweise y aufrufen könnte. (Und mit der wichtigen Eigenschaft, dass überladene Funktionen alle durch unterschiedliche Eckpunkte dargestellt werden - klingt so, als würde Clang das für Sie tun, Puh!)
j_random_hacker
1
@j_random_hacker: Tatsächlich ist die CFG komplizierter als ein einfacher Digraph, da sie den gesamten Code darstellt, der in Blöcken mit Verknüpfungen von einem Block zu einem anderen basierend auf bedingten Anweisungen ausgeführt werden soll. Der Hauptvorteil besteht darin, dass es natürlich zum Beschneiden von Code geeignet ist, der statisch als tot eingestuft werden kann (es werden nicht erreichbare Blöcke erstellt, die identifiziert werden können). Daher ist es besser, das CFG als das AST auszunutzen, um den Digraph zu erstellen, den Sie erstellen Apropos ... ich denke :)
Matthieu M.
1
@j_random_hacker: Tatsächlich macht Clangs AST alles explizit (oder fast ...), weil es für die Arbeit mit dem Code gedacht ist, nicht nur für das Kompilieren. Momentan gibt es tatsächlich eine Diskussion, da es anscheinend ein Problem mit Initialisierungslisten gibt, bei dem eine solche implizite Konvertierung nicht im AST angezeigt wird, aber ich denke, sie wird behoben.
Matthieu M.
35

Für den Fall nicht verwendeter ganzer Funktionen (und nicht verwendeter globaler Variablen) kann GCC tatsächlich den größten Teil der Arbeit für Sie erledigen, vorausgesetzt, Sie verwenden GCC und GNU ld.

Verwenden Sie beim Kompilieren der Quelle -ffunction-sectionsund -fdata-sectionsund dann beim Verknüpfen die Verwendung -Wl,--gc-sections,--print-gc-sections. Der Linker listet nun alle Funktionen auf, die entfernt werden konnten, weil sie nie aufgerufen wurden, und alle Globals, auf die nie verwiesen wurde.

(Natürlich können Sie den --print-gc-sectionsTeil auch überspringen und den Linker die Funktionen stillschweigend entfernen lassen, aber sie in der Quelle behalten.)

Hinweis: Hier werden nur nicht verwendete vollständige Funktionen gefunden, und es wird nichts gegen toten Code in Funktionen unternommen. Funktionen, die aus totem Code in Live-Funktionen aufgerufen werden, bleiben ebenfalls erhalten.

Einige C ++ - spezifische Funktionen verursachen ebenfalls Probleme, insbesondere:

  • Virtuelle Funktionen. Ohne zu wissen, welche Unterklassen vorhanden sind und welche zur Laufzeit tatsächlich instanziiert werden, können Sie nicht wissen, welche virtuellen Funktionen im endgültigen Programm vorhanden sein müssen. Der Linker hat nicht genügend Informationen darüber, so dass er sie alle behalten muss.
  • Globale mit Konstruktoren und ihren Konstruktoren. Im Allgemeinen kann der Linker nicht wissen, dass der Konstruktor für ein globales System keine Nebenwirkungen hat, daher muss er es ausführen. Dies bedeutet natürlich, dass auch das Globale selbst erhalten bleiben muss.

In beiden Fällen etwas verwendet , durch eine virtuelle Funktion oder eine globale Variablen Konstruktor hat auch gehalten um werden.

Eine zusätzliche Einschränkung besteht darin, dass beim Erstellen einer gemeinsam genutzten Bibliothek die Standardeinstellungen in GCC alle Funktionen in der gemeinsam genutzten Bibliothek exportieren , sodass sie für den Linker "verwendet" werden. Um dies zu beheben, müssen Sie standardmäßig festlegen, dass Symbole ausgeblendet werden, anstatt zu exportieren (z. B. -fvisibility=hidden), und dann die exportierten Funktionen, die Sie exportieren möchten, explizit auswählen.

Olsner
quelle
Toller praktischer Rat. Nur eine Liste der Funktionen zu erhalten, von denen bekannt ist, dass sie nirgendwo verwendet werden (auch wenn diese Liste, wie Sie sagen, nicht vollständig ist), wird meiner Meinung nach viel von den niedrig hängenden Früchten bringen.
j_random_hacker
Ich denke nicht, dass dies für unbegründete Vorlagen funktioniert .
Jakub Klinkovský
25

Wenn Sie g ++ verwenden, können Sie dieses Flag verwenden -Wunused

Laut Dokumentation:

Warnen Sie immer dann, wenn eine Variable außer ihrer Deklaration nicht verwendet wird, wenn eine Funktion als statisch deklariert, aber nie definiert wird, wenn eine Bezeichnung deklariert, aber nicht verwendet wird und wenn eine Anweisung ein Ergebnis berechnet, das explizit nicht verwendet wird.

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

Bearbeiten : Hier ist andere nützliche Flagge -Wunreachable-code Entsprechend Dokumentation:

Diese Option soll warnen, wenn der Compiler feststellt, dass mindestens eine ganze Zeile Quellcode niemals ausgeführt wird, weil eine bestimmte Bedingung niemals erfüllt ist oder weil sie nach einer Prozedur erfolgt, die niemals zurückkehrt.

Update : Ich habe ein ähnliches Thema gefunden. Erkennung von totem Code in einem älteren C / C ++ - Projekt

UmmaGumma
quelle
4
Dadurch werden keine Header abgefangen, deren Prototypfunktionen niemals aufgerufen werden. Oder öffentliche Klassenmethoden, die nicht aufgerufen werden. Es kann nur überprüft werden, ob in diesem Bereich Variablen mit lokalem Gültigkeitsbereich verwendet werden.
Falmarri
@ Falmarri Ich habe diese Flagge nie benutzt. Ich versuche selbst herauszufinden, welche toten Codes ich damit finden kann.
UmmaGumma
-Wunusedwarnt vor Variablen, die deklariert (oder auf einmal deklariert und definiert), aber tatsächlich nie verwendet werden. Übrigens ziemlich ärgerlich mit Scoped Guards: p Es gibt eine experimentelle Implementierung in Clang, die auch vor nichtflüchtigen Variablen warnt, die in Ted Kremenek geschrieben, aber nie von ihm gelesen werden (von Ted Kremenek). -Wunreachable-codewarnt vor Code innerhalb einer Funktion , die nicht erreicht werden kann, kann es Code , der sich nach einer sein throwoder returnAnweisung oder Code in einem Zweig, der nie genommen wird beispielsweise (die im Falle einer tautologischen Vergleiche passiert).
Matthieu M.
18

Ich denke, Sie suchen nach einem Tool zur Codeabdeckung . Ein Tool zur Codeabdeckung analysiert Ihren Code während der Ausführung und informiert Sie darüber, welche Codezeilen wie oft ausgeführt wurden und welche nicht.

Sie könnten versuchen, diesem Open-Source-Code-Coverage-Tool eine Chance zu geben: TestCocoon - Code-Coverage-Tool für C / C ++ und C #.

Carlos V.
quelle
7
Der Schlüssel hier ist "wie es läuft" - wenn Ihre Eingabedaten keinen Codepfad ausüben, wird dieser Pfad nicht als verwendet erkannt, oder?
Scharfzahn
1
Das ist richtig. Ohne Ausführen des Codes kann nicht festgestellt werden, welche Zeilen nicht erreicht werden. Ich frage mich, wie schwierig es sein wird, einige Unit-Tests einzurichten, um einige normale Läufe zu emulieren.
Carlos V
1
@drhishch Ich denke, dass die meisten dieser nicht verwendeten Codes Linker und nicht Compiler finden müssen.
UmmaGumma
1
@drhirsch Richtig, der Compiler kann sich um einen Teil des Codes kümmern, der nicht erreichbar ist, z. B. Funktionen, die deklariert, aber nicht aufgerufen werden, und einige Kurzschlussauswertungen. Aber was ist mit Code, der von Benutzeraktionen abhängt, oder Laufzeitvariablen?
Carlos V
1
@golcarcol Ok, lass uns eine Funktion void func()in a.cpp haben, die in b.cpp verwendet wird. Wie kann der Compiler überprüfen, ob func () im Programm verwendet wird? Es ist Linker Job.
UmmaGumma
15

Die wirkliche Antwort hier ist: Man kann nie wirklich sicher wissen.

Zumindest für nicht triviale Fälle können Sie nicht sicher sein, ob Sie alles bekommen haben. Beachten Sie Folgendes aus dem Wikipedia-Artikel über nicht erreichbaren Code :

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

Wie Wikipedia richtig bemerkt, kann ein cleverer Compiler möglicherweise so etwas fangen. Aber überlegen Sie sich eine Modifikation:

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

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

Wird der Compiler dies erfassen? Vielleicht. Dazu muss jedoch mehr getan werden, als nur sqrteinem konstanten Skalarwert zu begegnen. Es muss herausgefunden werden, dass (double)yes sich immer um eine Ganzzahl handelt (einfach), und dann muss der mathematische Bereich sqrtfür die Menge der Ganzzahlen (schwer) verstanden werden. Ein sehr hoch entwickelter Compiler kann dies möglicherweise für die sqrtFunktion oder für jede Funktion in math.h oder für jede Funktion mit fester Eingabe tun , deren Domäne er herausfinden kann. Dies wird sehr, sehr komplex und die Komplexität ist im Grunde unbegrenzt. Sie können Ihrem Compiler immer mehr Komplexitätsebenen hinzufügen, aber es gibt immer eine Möglichkeit, Code einzuschleusen, der für einen bestimmten Satz von Eingaben nicht erreichbar ist.

Und dann gibt es die Eingabesätze, die einfach nie eingegeben werden. Eingaben, die im wirklichen Leben keinen Sinn ergeben oder von der Validierungslogik an anderer Stelle blockiert werden. Der Compiler kann davon nichts wissen.

Das Endergebnis davon ist, dass die von anderen erwähnten Softwaretools zwar äußerst nützlich sind, Sie jedoch nie sicher wissen werden, dass Sie alles abgefangen haben, es sei denn, Sie gehen den Code anschließend manuell durch. Selbst dann werden Sie nie sicher sein, dass Sie nichts verpasst haben.

Die einzige wirkliche Lösung, IMHO, besteht darin, so wachsam wie möglich zu sein, die Ihnen zur Verfügung stehende Automatisierung zu nutzen, wo Sie können umzugestalten und ständig nach Möglichkeiten zu suchen, Ihren Code zu verbessern. Natürlich ist es trotzdem eine gute Idee, das zu tun.

Justin Morgan
quelle
1
Richtig und lass keinen toten Code drin! Wenn Sie eine Funktion entfernen, beenden Sie den toten Code. Wenn Sie es "nur für den Fall" dort lassen, wird es nur aufgebläht, was (wie Sie bereits besprochen haben) später schwer zu finden ist. Lassen Sie die Versionskontrolle das Horten für Sie erledigen.
Leichtigkeitsrennen im Orbit
12

Ich habe es selbst nicht benutzt, aber cppcheck behauptet, nicht verwendete Funktionen zu finden. Es wird wahrscheinlich nicht das gesamte Problem lösen, aber es könnte ein Anfang sein.

Herr Hai
quelle
Ja, es kann lokale, nicht referenzierte Variablen und Funktionen finden.
Chugaister
Ja, verwenden Sie cppcheck --enable=unusedFunction --language=c++ ., um diese nicht verwendeten Funktionen zu finden.
Jason Harris
9

Sie können versuchen, PC-lint / FlexeLint von Gimple Software zu verwenden . Es behauptet zu

Suchen Sie im gesamten Projekt nach nicht verwendeten Makros, Typedefs, Klassen, Mitgliedern, Deklarationen usw.

Ich habe es für statische Analysen verwendet und fand es sehr gut, aber ich muss zugeben, dass ich es nicht verwendet habe, um spezifisch toten Code zu finden.

Tony
quelle
5

Mein normaler Ansatz, unbenutzte Sachen zu finden, ist

  1. Stellen Sie sicher, dass das Build-System die Abhängigkeitsverfolgung korrekt verarbeitet
  2. Richten Sie einen zweiten Monitor mit einem Vollbild-Terminalfenster ein, auf dem wiederholte Builds ausgeführt werden und der erste Bildschirm mit der Ausgabe angezeigt wird. watch "make 2>&1"neigt dazu, den Trick unter Unix zu machen.
  3. Führen Sie eine Such- und Ersetzungsoperation für den gesamten Quellbaum aus und fügen Sie am Anfang jeder Zeile "//?" hinzu
  4. Beheben Sie den ersten vom Compiler gekennzeichneten Fehler, indem Sie das "//?" in den entsprechenden Zeilen.
  5. Wiederholen, bis keine Fehler mehr vorhanden sind.

Dies ist ein etwas langwieriger Prozess, der jedoch gute Ergebnisse liefert.

Simon Richter
quelle
2
Hat Verdienst, aber sehr arbeitsintensiv. Außerdem müssen Sie sicherstellen, dass alle Überladungen einer Funktion gleichzeitig auskommentiert werden. Wenn mehr als eine zutreffend ist, kann das Auskommentieren einer weniger bevorzugten Funktion die Kompilierung erfolgreich durchführen, jedoch zu einem falschen Programmverhalten führen (und zu einer falschen Vorstellung davon) Funktionen werden verwendet).
j_random_hacker
Ich kommentiere Deklarationen nur im ersten Schritt (alle Überladungen) aus und sehe dann in der nächsten Iteration, welche Definitionen fehlen; Auf diese Weise kann ich sehen, welche Überlastungen tatsächlich verwendet werden.
Simon Richter
@Simon: Interessanterweise weist MSalters in einem Kommentar zur Hauptfrage darauf hin, dass selbst das Vorhandensein / Fehlen einer Deklaration für eine Funktion, die niemals aufgerufen wird, Einfluss darauf haben kann, welche der beiden anderen Funktionen durch Überlastungsauflösung gefunden wird. Zugegeben, dies erfordert ein äußerst bizarres und ausgeklügeltes Setup, sodass es in der Praxis wahrscheinlich kein Problem darstellt.
j_random_hacker
4

Markieren Sie so viele öffentliche Funktionen und Variablen wie privat oder geschützt, ohne einen Kompilierungsfehler zu verursachen. Versuchen Sie dabei, auch den Code umzugestalten. Indem Sie Funktionen privat und teilweise geschützt machen, haben Sie Ihren Suchbereich verkleinert, da private Funktionen nur von derselben Klasse aufgerufen werden können (es sei denn, es gibt dumme Makros oder andere Tricks, um die Zugriffsbeschränkung zu umgehen, und wenn dies der Fall ist, würde ich Ihnen empfehlen einen neuen Job finden). Es ist viel einfacher festzustellen, dass Sie keine private Funktion benötigen, da nur die Klasse, an der Sie gerade arbeiten, diese Funktion aufrufen kann. Diese Methode ist einfacher, wenn Ihre Codebasis kleine Klassen hat und lose gekoppelt ist. Wenn Ihre Codebasis keine kleinen Klassen oder eine sehr enge Kopplung aufweist, empfehle ich, diese zuerst zu bereinigen.

Als nächstes werden alle verbleibenden öffentlichen Funktionen markiert und ein Aufrufdiagramm erstellt, um die Beziehung zwischen den Klassen herauszufinden. Versuchen Sie anhand dieses Baums herauszufinden, welcher Teil des Zweigs beschnitten werden kann.

Der Vorteil dieser Methode besteht darin, dass Sie sie auf Modulbasis ausführen können, sodass Sie Ihre Unittest problemlos weitergeben können, ohne lange Zeit zu haben, wenn Sie eine fehlerhafte Codebasis haben.

Lie Ryan
quelle
3

Wenn Sie unter Linux arbeiten, sollten Sie sich callgrindein C / C ++ - Programmanalysetool ansehen , das Teil der valgrindSuite ist und auch Tools enthält, die auf Speicherlecks und andere Speicherfehler prüfen (die Sie ebenfalls verwenden sollten). Es analysiert eine laufende Instanz Ihres Programms und erstellt Daten zu seinem Aufrufdiagramm sowie zu den Leistungskosten der Knoten im Aufrufdiagramm. Es wird normalerweise für die Leistungsanalyse verwendet, erstellt jedoch auch ein Anrufdiagramm für Ihre Anwendungen, sodass Sie sehen können, welche Funktionen aufgerufen werden und welche Aufrufer sie haben.

Dies ist offensichtlich eine Ergänzung zu den statischen Methoden, die an anderer Stelle auf der Seite erwähnt werden, und es wird nur hilfreich sein, um völlig unbenutzte Klassen, Methoden und Funktionen zu eliminieren - es hilft auch nicht, toten Code in Methoden zu finden, die tatsächlich aufgerufen werden.

Adam Higuera
quelle
3

Ich habe wirklich kein Tool verwendet, das so etwas tut ... Aber soweit ich in allen Antworten gesehen habe, hat noch niemand gesagt, dass dieses Problem nicht berechenbar ist.

Was meine ich damit? Dass dieses Problem von keinem Algorithmus jemals auf einem Computer gelöst werden kann. Dieser Satz (dass ein solcher Algorithmus nicht existiert) ist eine Folge von Turings Halteproblem.

Alle Werkzeuge, die Sie verwenden, sind keine Algorithmen, sondern Heuristiken (dh keine exakten Algorithmen). Sie geben Ihnen nicht genau den gesamten Code, der nicht verwendet wird.

Geekazoid
quelle
1
Ich denke, das OP möchte hauptsächlich Funktionen finden, die nicht von irgendwoher aufgerufen werden, was sicherlich nicht nicht berechenbar ist - die meisten modernen Linker können das! Es geht nur darum, diese Informationen mit dem geringsten Schmerz und der geringsten Plackerei zu extrahieren.
j_random_hacker
Du hast recht, ich habe den letzten Kommentar zur Hauptfrage nicht gesehen. Übrigens kann es Funktionen geben, auf die im Code verwiesen wird und die nicht tatsächlich verwendet werden. Solche Dinge werden möglicherweise nicht erkannt.
Geekazoid
2

Eine Möglichkeit besteht darin, einen Debugger und die Compiler-Funktion zu verwenden, um nicht verwendeten Maschinencode während der Kompilierung zu entfernen.

Sobald ein Teil des Maschinencodes entfernt ist, können Sie mit dem Debugger keinen Breakpojnt mehr in die entsprechende Zeile des Quellcodes einfügen. Sie setzen also überall Haltepunkte und starten das Programm und überprüfen die Haltepunkte - diejenigen, die sich im Status "Für diese Quelle kein Code geladen" befinden, entsprechen eliminiertem Code - entweder wird dieser Code nie aufgerufen oder er wurde inline gesetzt, und Sie müssen ein Minimum ausführen Analyse, um herauszufinden, welche dieser beiden passiert ist.

Zumindest funktioniert das in Visual Studio so, und ich denke, andere Toolsets können das auch.

Das ist viel Arbeit, aber ich denke schneller als die manuelle Analyse des gesamten Codes.

scharfer Zahn
quelle
4
Ich denke, die Frage des OP ist, wie man eine kleinere, besser verwaltbare Teilmenge des Quellcodes findet, und nicht so sehr, ob die kompilierte Binärdatei effizient ist.
j_random_hacker
@j_random_hacker Ich habe es versucht - und es stellte sich heraus, dass die Code-Eliminierung sogar zum Zurückverfolgen zum ursprünglichen Quellcode verwendet werden kann.
Scharfzahn
Müssen Sie bestimmte Compiler-Flags in Visual Studio verwenden, um dies zu erreichen? und funktioniert es nur im Release-Modus oder funktioniert es auch im Debug?
Naveen
Was ist mit Zeilen, die vom Compiler verwendet, aber optimiert werden?
Itamar Katz
@ Naveen: In Visual C ++ 9 müssen Sie die Optimierung
aktivieren
2

CppDepend ist ein kommerzielles Tool, das nicht verwendete Typen, Methoden und Felder erkennen und vieles mehr kann. Es ist für Windows und Linux verfügbar (bietet derzeit jedoch keine 64-Bit-Unterstützung) und wird mit einer zweiwöchigen Testversion geliefert.

Haftungsausschluss: Ich arbeite dort nicht, besitze jedoch eine Lizenz für dieses Tool (sowie NDepend , eine leistungsstärkere Alternative für .NET-Code).

Für diejenigen, die neugierig sind, ist hier ein Beispiel einer integrierten (anpassbaren) Regel zum Erkennen toter Methoden, die in CQLinq geschrieben wurde :

// <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] }
Roman Boiko
quelle
Update: 64-Bit-Unterstützung für Linux wurde in Version 3.1 hinzugefügt.
Roman Boiko
1

Dies hängt von der Plattform ab, auf der Sie Ihre Anwendung erstellen.

Wenn Sie beispielsweise Visual Studio verwenden, können Sie ein Tool wie .NET ANTS Profiler verwenden, mit dem Sie Ihren Code analysieren und profilieren können. Auf diese Weise sollten Sie schnell wissen, welcher Teil Ihres Codes tatsächlich verwendet wird. Eclipse hat auch äquivalente Plugins.

Andernfalls können Sie eine Protokolldatei für eine Prüfung verwenden, wenn Sie wissen müssen, welche Funktion Ihrer Anwendung tatsächlich von Ihrem Endbenutzer verwendet wird, und wenn Sie Ihre Anwendung problemlos freigeben können.

Für jede Hauptfunktion können Sie ihre Verwendung verfolgen und nach einigen Tagen / Woche einfach diese Protokolldatei abrufen und sie sich ansehen.

AUS
quelle
1
.net ANTS Profiler sieht aus wie für C # - sind Sie sicher, dass es auch für C ++ funktioniert?
j_random_hacker
@j_random_hacker: Solange ich weiß, funktioniert es mit verwaltetem Code. Daher wird .net ANTS sicherlich nicht in der Lage sein, 'Standard'-C ++ - Code zu analysieren (dh mit gcc kompiliert, ...).
AUS
0

Ich denke nicht, dass es automatisch gemacht werden kann.

Selbst mit Tools zur Codeabdeckung müssen Sie ausreichende Eingabedaten bereitstellen, um ausgeführt zu werden.

Möglicherweise ist ein sehr komplexes und hochpreisiges statisches Analysetool wie das des Coverity- oder LLVM-Compilers hilfreich.

Aber ich bin mir nicht sicher und würde eine manuelle Codeüberprüfung vorziehen.

AKTUALISIERT

Nun, nur nicht verwendete Variablen zu entfernen, nicht verwendete Funktionen sind jedoch nicht schwer.

AKTUALISIERT

Nachdem ich andere Antworten und Kommentare gelesen habe, bin ich stärker davon überzeugt, dass dies nicht möglich ist.

Sie müssen den Code kennen, um eine aussagekräftige Messung der Codeabdeckung zu erhalten, und wenn Sie wissen, dass viele manuelle Bearbeitungen schneller sind als das Vorbereiten / Ausführen / Überprüfen der Abdeckungsergebnisse.

9dan
quelle
2
Der Wortlaut Ihrer Antwort ist irreführend, LLVM ist nicht teuer ... es ist kostenlos!
Matthieu M.
Die manuelle Bearbeitung hilft Ihnen nicht bei Laufzeitvariablen, die logische Zweige in Ihrem Programm durchlaufen. Was ist, wenn Ihr Code niemals bestimmte Kriterien erfüllt und daher immer denselben Weg geht?
Carlos V
0

Ich hatte heute einen Freund, der mir genau diese Frage stellte, und ich sah mich einige vielversprechende Clang-Entwicklungen an, z. B. ASTMatcher und den Static Analyzer , die während des Kompilierens möglicherweise eine ausreichende Sichtbarkeit im Geschehen haben, um die toten Codeabschnitte zu bestimmen, aber dann ich Ich habe es gefunden:

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

Es ist so ziemlich eine vollständige Beschreibung der Verwendung einiger GCC-Flags, die anscheinend dazu dienen, nicht referenzierte Symbole zu identifizieren!

Steven Lu
quelle
0

Das allgemeine Problem, ob eine Funktion aufgerufen wird, ist NP-Complete. Sie können im Allgemeinen nicht im Voraus wissen, ob eine Funktion aufgerufen wird, da Sie nicht wissen, ob eine Turing-Maschine jemals anhalten wird. Sie können feststellen, ob es einen Pfad (statisch) gibt, der von main () zu der von Ihnen geschriebenen Funktion führt, aber das garantiert nicht, dass er jemals aufgerufen wird.

Luis Colorado
quelle
-3

Nun, wenn Sie g ++ verwenden, können Sie dieses Flag -Wunused verwenden

Laut Dokumentation:

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

Bearbeiten: Hier ist andere nützliche Flagge -Wunreachable-Code Gemäß Dokumentation:

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.
ram singh
quelle
6
Diese genauen Informationen wurden bereits in der derzeit am besten bewerteten Antwort erwähnt. Bitte lesen Sie die vorhandenen Antworten, um unnötige Doppelarbeit zu vermeiden.
j_random_hacker
1
Jetzt können Sie sich Ihr Peer Pressure-Abzeichen verdienen!
Andrew Grimm