Tools zum Auffinden enthaltener Header, die nicht verwendet werden? [geschlossen]

72

Ich weiß, dass PC-Lint Sie über Header informieren kann, die enthalten sind, aber nicht verwendet werden. Gibt es andere Tools, die dies tun können, vorzugsweise unter Linux?

Wir haben eine große Codebasis, in der sich in den letzten 15 Jahren viele Funktionen verschoben haben, aber die verbleibenden # include-Direktiven werden selten entfernt, wenn die Funktionalität von einer Implementierungsdatei in eine andere verschoben wird, was uns zu diesem Zeitpunkt ein ziemlich gutes Durcheinander hinterlässt. Ich kann natürlich mühsam alle # include-Direktiven entfernen und mir vom Compiler mitteilen lassen, welche Direktiven wieder eingeschlossen werden sollen, aber ich würde das Problem lieber umgekehrt lösen - die nicht verwendeten finden - anstatt eine Liste der verwendeten neu zu erstellen.

Nick Bastin
quelle
2
Es ist notorisch schwer, etwas zu finden, das nicht da ist.
Dies ist ein Problem, auf das ich zuvor gestoßen bin und das noch keine 100% zuverlässige automatisierte Lösung gefunden hat. Ich bin gespannt, welche Antworten wir erhalten.
DaveR
1
@Neil: Im Allgemeinen stimmt das, aber in diesem speziellen Fall ist es nicht so schwer (abstrakt). Sie identifizieren "lediglich" alle Symbole in der Datei, vergleichen sie mit den Überschriften, die sie erfüllen, und entfernen dann die Überschriften, die in diesem Prozess nicht verwendet wurden. In Wirklichkeit ist es natürlich kompliziert, weil Sie einen C / C ++ - Parser benötigen und die Definition von "erforderlich" lockerer ist, als Sie diesen Prozess "einfach" machen möchten.
Nick Bastin
1
@Nick und dann haben Sie Header, die nur auf einer Plattform verwendet werden, oder wenn Sie in einer Konfiguration kompilieren, haben Sie Header, die alle ihre Symbole bereitstellen, indem sie private Header einschließen, die der Clientcode nicht direkt enthalten sollte. Sie haben Header, die einen anderen enthalten autark, aber Sie verwenden nicht die Schnittstelle, für die das andere Include benötigt wird, ...
AProgrammer
@AProgrammer: Nur auf einer Plattform verwendet zu werden, ist relativ einfach zu lösen - ein Analysetool wird sie sowieso sofort vorverarbeiten (was auch in Ihrem Fall "einige Konfigurationen" passieren sollte). Ich suche nicht nach Headern, die in der Datei aufgeführt, sondern ordnungsgemäß vorverarbeitet sind. Ich suche nach Headern, die im fertigen Objektcode eine völlig unnötige Quelle enthalten. Auch für private Header ist das in Ordnung - sie werden in den meisten Fällen immer noch "verwendet" (oder sie waren unnötig - eine nützliche Sache zu wissen).
Nick Bastin

Antworten:

30

HAFTUNGSAUSSCHLUSS: Mein Tagesjob besteht darin, für ein Unternehmen zu arbeiten, das statische Analysewerkzeuge entwickelt.

Es würde mich wundern, wenn die meisten (wenn nicht alle) statischen Analysetools keine Form der Überprüfung der Header-Nutzung hätten. Sie können diese Wikipedia-Seite verwenden, um eine Liste der verfügbaren Tools abzurufen und die Unternehmen per E-Mail zu fragen.

Einige Punkte, die Sie bei der Bewertung eines Tools berücksichtigen sollten:

Bei Funktionsüberladungen möchten Sie, dass alle Header mit Überladungen sichtbar sind, nicht nur der Header, der die Funktion enthält, die durch die Überlastungsauflösung ausgewählt wurde:

// f1.h
void foo (char);

// f2.h
void foo (int);


// bar.cc
#include "f1.h"
#include "f2.h"

int main ()
{
  foo (0);  // Calls 'foo(int)' but all functions were in overload set
}

Wenn Sie den Brute-Force-Ansatz wählen, entfernen Sie zuerst alle Header und fügen Sie sie erneut hinzu, bis sie kompiliert werden. Wenn zuerst 'f1.h' hinzugefügt wird, wird der Code kompiliert, aber die Semantik des Programms wurde geändert.

Eine ähnliche Regel gilt, wenn Sie Teil- und Spezialisierungen haben. Es spielt keine Rolle, ob die Spezialisierung ausgewählt ist oder nicht, Sie müssen sicherstellen, dass alle Spezialisierungen sichtbar sind:

// f1.h
template <typename T>
void foo (T);

// f2.h
template <>
void foo (int);

// bar.cc
#include "f1.h"
#include "f2.h"


int main ()
{
  foo (0);  // Calls specialization 'foo<int>(int)'
}

Was das Überlastungsbeispiel betrifft, kann der Brute-Force-Ansatz zu einem Programm führen, das zwar noch kompiliert wird, aber ein anderes Verhalten aufweist.

Eine andere verwandte Art der Analyse, auf die Sie achten können, ist die Überprüfung, ob Typen vorwärts deklariert werden können. Folgendes berücksichtigen:

// A.h
class A { };

// foo.h
#include "A.h"
void foo (A const &);

// bar.cc
#include "foo.h"

void bar (A const & a)
{
  foo (a);
}

Im obigen Beispiel ist die Definition von 'A' nicht erforderlich, daher kann die Header-Datei 'foo.h' so geändert werden, dass sie nur für 'A' eine Vorwärtsdeklaration enthält:

// foo.h
class A;
void foo (A const &);

Diese Art der Überprüfung reduziert auch die Header-Abhängigkeiten.

Richard Corden
quelle
Die meisten, die ich mir angesehen habe, haben keine Überprüfung der Header-Nutzung dieser Art. Sie machen einen sehr guten Punkt über Überladungen und Spezialisierungen, aber zum Glück sind unsere Konventionen so, dass diese im Grunde nie in verschiedenen Headern stehen würden.
Nick Bastin
Außerdem war ich mit dieser Wikipedia-Seite unterwegs. Der C / C ++ - Abschnitt ist sehr schwach ... Ich denke, ich sollte die Liste der kommerziellen Anbieter durchgehen und sehen, welche C ++ unterstützen. Ich bin auch vollkommen in Ordnung mit Leuten, die ihr eigenes Produkt vorschlagen - es ist mehr als ich vorher tun musste, und Ihr Rat ist im Allgemeinen sehr informativ.
Nick Bastin
"Bei Funktionsüberladungen möchten Sie, dass alle Header mit Überladungen sichtbar sind, nicht nur der Header, der die Funktion enthält, die durch die Überlastungsauflösung ausgewählt wurde: ..." +1, das ist ein potenzieller Albtraum zum Debuggen und ein wichtiger Grund, fürchte ich dies selbst zu tun
Andres Salas
22

Hier ist ein Skript, das es tut:

#!/bin/bash
# prune include files one at a time, recompile, and put them back if it doesn't compile
# arguments are list of files to check
removeinclude() {
    file=$1
    header=$2
    perl -i -p -e 's+([ \t]*#include[ \t][ \t]*[\"\<]'$2'[\"\>])+//REMOVEINCLUDE $1+' $1
}
replaceinclude() {
   file=$1
   perl -i -p -e 's+//REMOVEINCLUDE ++' $1
}

for file in $*
do
    includes=`grep "^[ \t]*#include" $file | awk '{print $2;}' | sed 's/[\"\<\>]//g'`
    echo $includes
    for i in $includes
    do
        touch $file # just to be sure it recompiles
        removeinclude $file $i
        if make -j10 >/dev/null  2>&1;
        then
            grep -v REMOVEINCLUDE $file > tmp && mv tmp $file
            echo removed $i from $file
        else
            replaceinclude $file
            echo $i was needed in $file
        fi
    done
done
Andy C.
quelle
Ich habe die gleiche Methode selbst verwendet. Wenn Sie GCC verwenden, müssen -Werror=missing-prototypesSie die Header zu Funktionen entfernen, die in der Quelldatei definiert sind und später Probleme verursachen können (Sie werden nicht bemerken, wenn der Header nicht mehr synchron ist).
ideasman42
nett! wird wahrscheinlich bei einem großen Projekt nicht gut skalieren, aber genau das, was ich für ein kleines Projekt brauchte! Vielen Dank! Allerdings .. ich denke, dass es zuerst auf all.h und dann auf allen cpp ausgeführt werden sollte ...
Stefano
5

Schauen Sie sich Dehydra an .

Von der Website:

Dehydra ist ein leichtes, skriptfähiges statisches Allzweck-Analysetool, das anwendungsspezifische Analysen von C ++ - Code ermöglicht. Im einfachsten Sinne kann Dehydra als semantisches Grep-Tool betrachtet werden.

Es sollte möglich sein, ein Skript zu erstellen, das nach nicht verwendeten # include-Dateien sucht.

Ton van den Heuvel
quelle
Die Entwicklung wurde im Jahr 2010 aufgegeben.
Mathe
Die Entwicklung wurde auf DXR geändert .
Camille Goudeseune
5

Googles cppclean scheint gute Arbeit zu leisten, um nicht verwendete Header-Dateien zu finden. Ich habe gerade angefangen, es zu benutzen. Es werden einige Fehlalarme erzeugt. In Header-Dateien werden häufig unnötige Includes gefunden. Es wird jedoch nicht angegeben, dass Sie eine Vorwärtsdeklaration der zugeordneten Klasse benötigen und das Include in die zugehörige Quelldatei verschoben werden muss.

Chance
quelle
cppcleanreinigt zu viel. Wenn ich eine Header-Datei habe foo.h, die explizit Funktionen und Typen verwendet, die in bar.hund in definiert sind, baz.hwürde ich erwarten, foo.hdass a #include "bar.h"und a angezeigt werden #include "baz.h". Angenommen, das bar.hpassiert auch #include "baz.h". Dies bedeutet nicht , dass ich loswerden der bekommen kann #include "baz.h"in foo.h. Die meisten nicht benötigten Header-Dateiprüfungen sagen, dass ich es loswerden sollte. Dies ist ein falsches Positiv, das fast so schlecht ist wie falsches Negativ. (Und vielleicht noch schlimmer; zu viele Fehlalarme und ich werde das Tool nicht mehr verwenden. Lint ist ein gutes Beispiel.)
David Hammen
@ David, ich bin damit einverstanden, dass es viele falsch positive Ergebnisse erzeugt, aber ich denke, dass es besser ist, als jede Datei manuell zu untersuchen, und falsch positive Ergebnisse werden schnell erkannt und behoben. Haben Sie etwas, das Sie verwenden und das ziemlich gut funktioniert?
Chance
3
Heute kann meine Akte ohne auskommen #include "baz.h". Morgen vielleicht nicht. Angenommen, es bar.hliegt in Ihrer Verantwortung und auch Sie sind dabei, nicht benötigte Header zu entfernen. Sie bar.hbrauchen nicht baz.h, also löschen Sie das Fremde #include "baz.h"aus bar.h. Sie haben gerade jeden Code gebrochen, der auf diesem Fremdkörper huckepack genommen hat #include. Die Lösung besteht darin, sich nicht auf solche Huckepack-Geräte zu verlassen. Wenn Ihre Datei eine an anderer Stelle #includedefinierte Funktionalität verwendet, die Datei, die diese Funktionalität definiert. Lassen Sie das nicht von einem anderen Header #includefür Sie tun .
David Hammen
@DavidHammen, wenn ich mich nicht darauf verlassen möchte, Huckepack einzuschließen, ist dann nicht ein Tool wie cppclean der richtige Weg, dies zu tun? Ja, wenn ich einen unnötigen Header entferne, werden viele Dinge kaputt gehen, aber diese Dateien sollten eigentlich nicht das Huckepack enthalten. Also gehe ich und repariere die Dateien, die sich darauf verlassen haben. Wenn ich wirklich ein besseres Abhängigkeitsmanagement möchte, sollte ich das sowieso tun. Ich bin verwirrt, weil Sie sagen, Huckepack ist schlecht, aber dann sagen Sie, ich soll kein Werkzeug verwenden, das mich zwingt, sie zu beseitigen.
Chance
Ich glaube nicht, dass Sie das Problem verstehen. Automatisierte Werkzeuge übersehen manchmal das Offensichtliche, gehen manchmal zu weit. Sie geben falsch positive und falsch negative. Sie können solche Werkzeuge verwenden, aber nehmen Sie sie immer mit einem Körnchen Salz.
David Hammen
3

Wenn Sie Eclipse CDT verwenden, können Sie Includator ausprobieren, das für Betatester (zum Zeitpunkt dieses Schreibens) kostenlos ist und überflüssige #includes automatisch entfernt oder fehlende hinzufügt.

Haftungsausschluss: Ich arbeite für das Unternehmen, das Includator entwickelt, und benutze es seit einigen Monaten. Es funktioniert ganz gut für mich, also probieren Sie es aus :-)

Mirko Stocker
quelle
1

Soweit ich weiß, gibt es keinen (der nicht PC-Lint ist), was schade und überraschend ist. Ich habe den Vorschlag gesehen, diesen Pseudocode zu verwenden (der im Grunde Ihren "sorgfältigen Prozess" automatisiert:

für jede cpp-Datei
für jeden Header-Include-
Kommentar aus dem Include
kompilieren Sie die cpp-Datei,
wenn (compile_errors)
den Header auskommentieren ,
andernfalls
entfernen Sie den Header-Include aus cpp

Legen Sie das in einen nächtlichen Cron, und es sollte die Arbeit erledigen und das betreffende Projekt frei von nicht verwendeten Headern halten (Sie können es natürlich immer manuell ausführen, aber die Ausführung wird lange dauern). Das einzige Problem ist, wenn ohne Header kein Fehler generiert wird, aber dennoch Code erzeugt wird.

Cinder6
quelle
1
Das bereinigt leider immer noch keine Header, die andere Header enthalten, die nicht benötigt werden (und schlimmer noch, es kann zu einer "zufälligen Programmierung" in anderen Implementierungsdateien kommen, die die benötigten Header über andere Header abrufen, die sie tatsächlich nicht benötigen ). Es reduziert zumindest die Anzahl der falschen Includes in CPP-Dateien, aber ich möchte sie auch in anderen Headern entfernen.
Nick Bastin
3
Es ist auch nicht ratsam, jeden Header zu entfernen. Betrachten Sie #include <vector> und #include <algorithm>. In einigen Implementierungen wird ein Vektoralgorithmus enthalten sein, dies ist jedoch nicht garantiert. Robuster Code sollte beide enthalten (falls beide verwendet werden). Ihre beschriebene Methode könnte #include <algorithm> abhängig von der Implementierung des Vektors entfernen.
Deft_code
Das ist wahr. Nick, beschäftigst du dich mehr mit lokalen Header-Dateien (oder hast du zumindest viele davon)? In diesem Fall können Sie den obigen Algorithmus so ändern, dass er nicht mit Bibliotheksheadern in Konflikt gerät, und diese manuell optimieren. Es ist ein Schmerz, aber es würde zumindest die Arbeit reduzieren.
Cinder6
1

Ich habe dies manuell gemacht und es lohnt sich kurzfristig (Oh, ist es langfristig? - Es dauert lange), da die Kompilierungszeit verkürzt ist:

  1. Für jede CPP-Datei müssen weniger Header analysiert werden.
  2. Weniger Abhängigkeiten - die ganze Welt muss nach einer Änderung an einem Header nicht neu kompiliert werden.

Es ist auch eine rekursive Prozess - jeder Headerdatei , die Aufenthalte in Prüfung des Bedürfnisses zu sehen , ob die Header - Dateien es entfernt werden kann enthält. Außerdem können Sie manchmal Forward-Deklarationen für Header-Includes ersetzen.

Dann muss der gesamte Prozess alle paar Monate / Jahr wiederholt werden, um den Überblick über die verbleibenden Header zu behalten.

Eigentlich ärgere ich mich ein bisschen über C ++ - Compiler, sie sollten Ihnen sagen können, was nicht benötigt wird - der Microsoft-Compiler kann Ihnen sagen, wann eine Änderung an einer Header-Datei beim Kompilieren sicher ignoriert werden kann.

Quamrana
quelle
0

Wenn jemand interessiert ist, habe ich gerade ein kleines Java-Befehlszeilentool auf sourceforge geputtet, um genau das zu tun. Da es in Java geschrieben ist, kann es offensichtlich unter Linux ausgeführt werden.

Der Link für das Projekt lautet https://sourceforge.net/projects/chksem/files/chksem-1.0/

Arctica
quelle
-1

Die meisten Ansätze zum Entfernen nicht verwendeter Funktionen funktionieren besser, wenn Sie zunächst sicherstellen, dass jede Ihrer Header-Dateien für sich kompiliert wird. Ich habe das relativ schnell wie folgt gemacht (Entschuldigung für Tippfehler - ich schreibe das zu Hause:

find . -name '*.h' -exec makeIncluder.sh {} \;

wo makeIncluder.shenthält:

#!/bin/sh
echo "#include \"$1\"" > $1.cpp

Bei ./subdir/classname.hdiesem Ansatz wird für jede Datei eine Datei erstellt, ./subdir/classname.h.cppdie die Zeile enthält

#include "./subdir/classname.h"

Wenn Sie makefilein der. Das Verzeichnis kompiliert alle CPP-Dateien und enthält -I.. Durch erneutes Kompilieren wird getestet, ob jede Include-Datei für sich kompiliert werden kann. Kompilieren Sie in Ihrer bevorzugten IDE mit goto-error und beheben Sie die Fehler.

Wenn du fertig bist, find . -name '*.h.cpp' -exec rm {} \;

John Hainsworth
quelle
1
Ich sehe nicht, wie das hilft. Selbst wenn einige meiner Header nicht selbst kompiliert würden (was sowieso nicht der Fall ist), würde dies keinen zusätzlichen Einblick in die nicht verwendeten geben.
Nick Bastin