Wie soll ich unnötige # include-Dateien in einem großen C ++ - Projekt erkennen?

96

Ich arbeite an einem großen C ++ - Projekt in Visual Studio 2008, und es gibt viele Dateien mit unnötigen #includeAnweisungen. Manchmal sind die #includes nur Artefakte und alles wird gut kompiliert, wenn sie entfernt werden. In anderen Fällen können Klassen vorwärts deklariert und das #include in die .cppDatei verschoben werden. Gibt es gute Werkzeuge, um diese beiden Fälle zu erkennen?

chaotisch
quelle

Antworten:

50

Während nicht benötigte Include-Dateien nicht angezeigt werden, verfügt Visual Studio über eine Einstellung /showIncludes(Rechtsklick auf eine .cppDatei Properties->C/C++->Advanced), die beim Kompilieren einen Baum aller enthaltenen Dateien ausgibt. Dies kann bei der Identifizierung von Dateien hilfreich sein, die nicht enthalten sein müssen.

Sie können sich auch die Pimpl-Sprache ansehen, um weniger Abhängigkeiten von Header-Dateien zu vermeiden und die Cruft, die Sie entfernen können, besser erkennen zu können.

Finsternis
quelle
1
/ showincludes ist großartig. Dies manuell zu tun war ohne das entmutigend.
Shambolic
30

PC Lint funktioniert hierfür recht gut und findet auch für Sie alle möglichen anderen albernen Probleme. Es verfügt über Befehlszeilenoptionen, mit denen externe Tools in Visual Studio erstellt werden können. Ich habe jedoch festgestellt, dass das Visual Lint- Add-In einfacher zu bearbeiten ist. Auch die kostenlose Version von Visual Lint hilft. Aber probieren Sie PC-Lint aus. Die Konfiguration so, dass Sie nicht zu viele Warnungen erhalten, dauert einige Zeit, aber Sie werden erstaunt sein, was dabei herauskommt.

Joe
quelle
3
Einige Anweisungen, wie dies mit PC-Lint zu tun ist, finden Sie auf riverblade.co.uk/…
David Sykes
26

!!HAFTUNGSAUSSCHLUSS!! Ich arbeite an einem kommerziellen statischen Analysetool (nicht an PC Lint). !!HAFTUNGSAUSSCHLUSS!!

Bei einem einfachen Nicht-Parsing-Ansatz gibt es mehrere Probleme:

1) Überlastsätze:

Es ist möglich, dass eine überladene Funktion Deklarationen enthält, die aus verschiedenen Dateien stammen. Es kann sein, dass das Entfernen einer Header-Datei dazu führt, dass eine andere Überladung anstelle eines Kompilierungsfehlers ausgewählt wird! Das Ergebnis wird eine stille Änderung der Semantik sein, die danach möglicherweise nur sehr schwer aufzuspüren ist.

2) Vorlagenspezialisierungen:

Ähnlich wie im Überladungsbeispiel möchten Sie, wenn Sie teilweise oder explizite Spezialisierungen für eine Vorlage haben, dass alle sichtbar sind, wenn die Vorlage verwendet wird. Möglicherweise befinden sich Spezialisierungen für die primäre Vorlage in verschiedenen Header-Dateien. Das Entfernen des Headers mit der Spezialisierung führt nicht zu einem Kompilierungsfehler, kann jedoch zu einem undefinierten Verhalten führen, wenn diese Spezialisierung ausgewählt worden wäre. (Siehe: Sichtbarkeit der Vorlagenspezialisierung der C ++ - Funktion )

Wie von 'msalters' hervorgehoben, ermöglicht die Durchführung einer vollständigen Analyse des Codes auch die Analyse der Klassenverwendung. Durch Überprüfen, wie eine Klasse über einen bestimmten Dateipfad verwendet wird, kann die Definition der Klasse (und damit alle ihre Abhängigkeiten) vollständig entfernt oder zumindest auf eine Ebene verschoben werden, die näher an der Hauptquelle im Include liegt Baum.

Richard Corden
quelle
@RichardCorden: Ihre Software (QA C ++) ist zu teuer.
Xander Tulip
13
@XanderTulip: Es ist schwer, darauf zu reagieren, ohne in einem Verkaufsgespräch zu landen - also entschuldige ich mich im Voraus. IMHO, was Sie berücksichtigen müssen, ist, wie lange ein guter Ingenieur brauchen würde, um solche Dinge (sowie viele andere Sprach- / Kontrollflussfehler) in einem Projekt mit angemessener Größe zu finden. Wenn sich die Software ändert, muss dieselbe Aufgabe immer wieder wiederholt werden. Wenn Sie also die Zeitersparnis berechnen, sind die Kosten des Tools wahrscheinlich nicht signifikant.
Richard Corden
10

Ich kenne keine solchen Tools und habe in der Vergangenheit darüber nachgedacht, eines zu schreiben, aber es stellt sich heraus, dass dies ein schwer zu lösendes Problem ist.

Angenommen, Ihre Quelldatei enthält ah und bh. ah enthält #define USE_FEATURE_Xund bh verwendet #ifdef USE_FEATURE_X. Wenn dies #include "a.h"auskommentiert ist, wird Ihre Datei möglicherweise noch kompiliert, tut jedoch möglicherweise nicht das, was Sie erwarten. Dies programmgesteuert zu erkennen ist nicht trivial.

Unabhängig davon, welches Tool dies tut, muss auch Ihre Build-Umgebung bekannt sein. Wenn ah aussieht wie:

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

Dann USE_FEATURE_Xwird nur definiert, wenn WINNTdefiniert ist, sodass das Tool wissen muss, welche Anweisungen vom Compiler selbst generiert werden und welche im Befehl compile und nicht in einer Header-Datei angegeben sind.

Graeme Perrow
quelle
9

Wie Timmermans kenne ich keine Werkzeuge dafür. Aber ich kenne Programmierer, die ein Perl- (oder Python-) Skript geschrieben haben, um zu versuchen, jede Include-Zeile einzeln zu kommentieren und dann jede Datei zu kompilieren.


Es scheint, dass Eric Raymond jetzt ein Werkzeug dafür hat .

In cpplint.py von Google gibt es (unter anderem) die Regel " Einschließen, was Sie verwenden", aber soweit ich das beurteilen kann, nein " Nur einschließen , was Sie verwenden". Trotzdem kann es nützlich sein.

Max Lybbert
quelle
Ich musste lachen, als ich diesen las. Mein Chef hat genau das bei einem unserer Projekte im letzten Monat getan. Der reduzierte Header enthält einige Faktoren.
Don Wakefield
2
Codewarrior auf dem Mac hatte früher ein Skript eingebaut, um dies zu tun. Es hat nur für #includes am Anfang einer Datei funktioniert, aber dort befinden sie sich normalerweise. Es ist nicht perfekt, aber es hält die Dinge einigermaßen gesund.
Slycrel
5

Wenn Sie sich allgemein für dieses Thema interessieren, sollten Sie sich Lakos ' Large Scale C ++ Software Design ansehen . Es ist etwas veraltet, geht aber auf viele "physische Design" -Probleme ein, wie das Finden des absoluten Minimums an Headern, die eingeschlossen werden müssen. Ich habe so etwas nirgendwo anders gesehen.

Adrian
quelle
4

Probieren Sie Include Manager aus . Es lässt sich problemlos in Visual Studio integrieren und visualisiert Ihre Include-Pfade, sodass Sie unnötige Inhalte finden können. Intern wird Graphviz verwendet, aber es gibt noch viele weitere coole Funktionen. Und obwohl es ein kommerzielles Produkt ist, hat es einen sehr niedrigen Preis.

Alex
quelle
3

Wenn Ihre Header-Dateien im Allgemeinen mit beginnen

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

(im Gegensatz zur einmaligen Verwendung von #pragma) Sie können dies ändern in:

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

Und da der Compiler den Namen der zu kompilierenden CPP-Datei ausgibt, würden Sie zumindest wissen, durch welche CPP-Datei der Header mehrmals eingefügt wird.

Sam
quelle
12
Ich denke, es ist in Ordnung, Header mehrmals einzuschließen. Es ist gut, das einzuschließen, was Sie verwenden, und nicht von Ihren Include-Dateien abhängig zu sein. Ich denke, das OP möchte #includes finden, die nicht wirklich verwendet werden.
Ryan Ginstrom
12
IMO aktiv falsch zu tun. Header sollten andere Header enthalten, wenn sie ohne sie nicht funktionieren würden. Und wenn Sie haben A.hund B.hdass beide davon abhängen C.hund Sie einschließen, A.hund B.hweil Sie beide benötigen, werden Sie zweimal einschließen , aber das ist in Ordnung, weil der Compiler es das zweite Mal überspringt und wenn Sie es nicht getan haben, müssten Sie sichC.h erinnern immer C.hvor A.hoder am B.hEnde in viel nutzloseren Einschlüssen einzuschließen.
Jan Hudec
5
Der Inhalt ist korrekt. Dies ist eine gute Lösung, um Header zu finden, die mehrfach enthalten sind. Die ursprüngliche Frage wird damit jedoch nicht beantwortet, und ich kann mir nicht vorstellen, wann dies eine gute Idee wäre. Cpp-Dateien sollten alle Header enthalten, von denen sie abhängen, auch wenn der Header zuvor an einer anderen Stelle enthalten ist. Sie möchten nicht, dass Ihr Projekt auftragsspezifisch kompiliert wird oder dass ein anderer Header den von Ihnen benötigten enthält.
Jaypb
3

PC-Lint kann dies tatsächlich. Eine einfache Möglichkeit, dies zu tun, besteht darin, es so zu konfigurieren, dass nur nicht verwendete Include-Dateien erkannt und alle anderen Probleme ignoriert werden. Dies ist ziemlich einfach - um nur die Nachricht 766 ("Header-Datei wird im Modul nicht verwendet") zu aktivieren, fügen Sie einfach die Optionen -w0 + e766 in die Befehlszeile ein.

Der gleiche Ansatz kann auch für verwandte Nachrichten wie 964 ("Header-Datei wird nicht direkt im Modul verwendet") und 966 ("Indirekt enthaltene Header-Datei, die nicht im Modul verwendet wird") verwendet werden.

FWIW Ich habe darüber letzte Woche in einem Blog-Beitrag unter http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318 ausführlicher geschrieben .


quelle
2

Wenn Sie unnötige #includeDateien entfernen möchten , um die Erstellungszeiten zu verkürzen , sollten Sie Ihre Zeit und Ihr Geld besser für die Parallelisierung Ihres Erstellungsprozesses mit cl.exe / MP , make -j , Xoreax IncrediBuild , distcc / icecream verwenden usw. verwenden.

Wenn Sie bereits einen parallelen Erstellungsprozess haben und dennoch versuchen, ihn zu beschleunigen, bereinigen Sie auf jeden Fall Ihre #includeAnweisungen und entfernen Sie diese unnötigen Abhängigkeiten.

bk1e
quelle
2

Beginnen Sie mit jeder Include-Datei und stellen Sie sicher, dass jede Include-Datei nur das enthält, was zum Kompilieren selbst erforderlich ist. Alle Include-Dateien, die dann für die C ++ - Dateien fehlen, können zu den C ++ - Dateien selbst hinzugefügt werden.

Kommentieren Sie für jede Include- und Quelldatei jede Include-Datei einzeln aus und prüfen Sie, ob sie kompiliert wird.

Es ist auch eine gute Idee, die Include-Dateien alphabetisch zu sortieren. Wenn dies nicht möglich ist, fügen Sie einen Kommentar hinzu.

Selwyn
quelle
2
Ich bin mir nicht sicher, wie praktisch dieser Kommentar ist, wenn eine sehr große Anzahl von Implementierungsdateien beteiligt ist.
Sonny
1

Das Hinzufügen einer oder beider der folgenden #defines schließt häufig unnötige Header-Dateien aus und kann die Kompilierungszeiten erheblich verbessern, insbesondere wenn der Code keine Windows-API-Funktionen verwendet.

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

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

Roger Nelson
quelle
1
Beides ist nicht erforderlich - VC_EXTRALEAN definiert WIN32_LEAN_AND_MEAN
Aidan Ryan
1

Wenn Sie dies noch nicht getan haben, wird die Verwendung eines vorkompilierten Headers, der alles enthält, was Sie nicht ändern möchten (Plattform-Header, externe SDK-Header oder statische bereits abgeschlossene Teile Ihres Projekts), einen großen Unterschied in den Erstellungszeiten bewirken.

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

Auch wenn es für Ihr Projekt möglicherweise zu spät ist, ist es eine gute Praxis, Ihr Projekt in Abschnitte zu unterteilen und nicht alle lokalen Header in einem großen Hauptheader zusammenzufassen, obwohl dies ein wenig zusätzliche Arbeit erfordert.

anon6439
quelle
Gute Erklärung für vorkompilierte Header: cygnus-software.com/papers/precompiledheaders.html (Nicht sicher, ob die automatische Generierung vorkompilierter Header in neueren Versionen von VisualStudio fehlerhaft ist, aber es lohnt sich, sie zu überprüfen.)
idbrii
1

Wenn Sie mit Eclipse CDT arbeiten würden, könnten Sie http://includator.com ausprobieren, um Ihre Include-Struktur zu optimieren. Includator weiß jedoch möglicherweise nicht genug über die vordefinierten Includes von VC ++, und das Einrichten von CDT für die Verwendung von VC ++ mit korrekten Includes ist noch nicht in CDT integriert.

PeterSom
quelle
1

Die neueste Jetbrains-IDE, CLion, zeigt automatisch (in grau) die Includes an, die in der aktuellen Datei nicht verwendet werden.

Es ist auch möglich, die Liste aller nicht verwendeten Includes (sowie Funktionen, Methoden usw.) aus der IDE abzurufen.

Jean-Michaël Celerier
quelle
0

Einige der vorhandenen Antworten besagen, dass es schwierig ist. Dies ist in der Tat richtig, da Sie einen vollständigen Compiler benötigen, um die Fälle zu ermitteln, in denen eine Vorwärtsdeklaration angemessen wäre. Sie können C ++ nicht analysieren, ohne zu wissen, was die Symbole bedeuten. Die Grammatik ist dafür einfach zu vieldeutig. Sie müssen wissen, ob ein bestimmter Name eine Klasse (könnte vorwärts deklariert werden) oder eine Variable (kann nicht) benennt. Außerdem müssen Sie Namespace-fähig sein.

MSalters
quelle
Man könnte einfach sagen "Die Entscheidung, welche #includes notwendig sind, ist gleichbedeutend mit der Lösung des Halteproblems. Viel Glück :)" Natürlich können Sie Heuristiken verwenden, aber ich kenne keine freie Software, die dies tut.
porges
0

Wenn es einen bestimmten Header gibt, von dem Sie glauben, dass er nicht mehr benötigt wird (z. B. string.h), können Sie das Include auskommentieren und dann unter alle Includes setzen:

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

Natürlich verwenden Ihre Schnittstellen-Header möglicherweise eine andere # Define-Konvention, um ihre Aufnahme in den CPP-Speicher aufzuzeichnen. Oder keine Konvention. In diesem Fall funktioniert dieser Ansatz nicht.

Dann wieder aufbauen. Es gibt drei Möglichkeiten:

  • Es baut sich gut auf. string.h war nicht kompilierungskritisch und das Include dafür kann entfernt werden.

  • Die #Fehlerauslösungen. string.g wurde irgendwie indirekt aufgenommen. Sie wissen immer noch nicht, ob string.h erforderlich ist. Wenn es erforderlich ist, sollten Sie es direkt einschließen (siehe unten).

  • Sie erhalten einen anderen Kompilierungsfehler. string.h wurde benötigt und wird nicht indirekt eingeschlossen, daher war das Include zunächst korrekt.

Beachten Sie, dass abhängig von der indirekten Einbeziehung, wenn Ihre .h- oder .c-Datei direkt eine andere .h-Datei verwendet, mit ziemlicher Sicherheit ein Fehler vorliegt: Sie versprechen tatsächlich, dass Ihr Code diesen Header nur benötigt, solange ein anderer von Ihnen verwendeter Header dies erfordert. Das ist wahrscheinlich nicht das, was du gemeint hast.

Die in anderen Antworten erwähnten Vorbehalte zu Headern, die das Verhalten ändern, anstatt Dinge zu deklarieren, die Buildfehler verursachen, gelten auch hier.

Britton Kerin
quelle