Come posso rilevare i file #include non necessari in un grande progetto C ++?

Sto lavorando ad un grande progetto C ++ in Visual Studio 2008, e ci sono molti file con direttive #include non necessarie. A volte i #include sono solo artefatti e tutto si compila bene con loro rimossi, e in altri casi le classi possono essere inoltrate e il #include può essere spostato nel file .cpp . Esistono buoni strumenti per rilevare entrambi questi casi?

Sebbene non mostri i file di inclusione non necessari, Visual Studio ha un’impostazione /showIncludes (fai clic con il pulsante destro del mouse su un file .cpp , Properties->C/C++->Advanced ) che visualizzerà un albero di tutti i file inclusi in fase di compilazione. Questo può aiutare a identificare i file che non dovrebbero essere inclusi.

Puoi anche dare un’occhiata all’idioma di Pimpl per farti scappare con meno dipendenze dei file di intestazione per rendere più facile vedere il cruft che puoi rimuovere.

PC Lint funziona abbastanza bene per questo, e trova tutti i tipi di altri problemi goofy anche per te. Dispone di opzioni a riga di comando che possono essere utilizzate per creare strumenti esterni in Visual Studio, ma ho scoperto che l’addin di Visual Lint è più semplice da utilizzare. Anche la versione gratuita di Visual Lint aiuta. Ma dai un colpo a PC-Lint. Configurarlo in modo che non ti dia troppi avvisi richiede un po ‘di tempo, ma sarai sbalordito da ciò che si presenta.

C’è un nuovo strumento basato su Clang, include-what-you-use , che mira a farlo.

!! NEGAZIONE !! Lavoro su uno strumento di analisi statica commerciale (non su PC Lint). !! NEGAZIONE !!

Ci sono diversi problemi con un semplice approccio non di analisi:

1) Set di sovraccarico:

È ansible che una funzione sovraccaricata abbia dichiarazioni provenienti da file diversi. Potrebbe essere che la rimozione di un file di intestazione porti a un sovraccarico differente scelto piuttosto che a un errore di compilazione! Il risultato sarà un cambiamento silenzioso nella semantica che potrebbe essere molto difficile da rintracciare in seguito.

2) Specializzazioni modello:

Simile all’esempio di sovraccarico, se si dispone di specializzazioni parziali o esplicite per un modello, si desidera che siano tutti visibili quando si utilizza il modello. Potrebbe essere che le specializzazioni per il modello principale siano in file di intestazione diversi. La rimozione dell’intestazione con la specializzazione non causerà un errore di compilazione, ma potrebbe comportare un comportamento indefinito se tale specializzazione fosse stata selezionata. (Vedi: Visibilità della specializzazione template della funzione C ++ )

Come sottolineato da “msalters”, l’esecuzione di un’analisi completa del codice consente anche l’analisi dell’uso della class. Controllando il modo in cui una class viene utilizzata attraverso un percorso specifico di file, è ansible che la definizione della class (e quindi tutte le sue dipendenze) possa essere rimossa completamente o almeno spostata a un livello più vicino alla sorgente principale nell’inclusione albero.

Non conosco alcuno di questi strumenti, e ho pensato di scriverne uno in passato, ma si scopre che questo è un problema difficile da risolvere.

Supponiamo che il tuo file sorgente includa ah e bh; ah contiene #define USE_FEATURE_X e bh usa #ifdef USE_FEATURE_X . Se #include "ah" è commentato, il tuo file potrebbe ancora compilare, ma potrebbe non fare ciò che ti aspetti. Rilevare questo programmaticamente non è banale.

Qualunque strumento faccia questo dovrebbe conoscere anche il tuo ambiente di costruzione. Se ah sembra:

 #if defined( WINNT ) #define USE_FEATURE_X #endif 

Quindi USE_FEATURE_X viene definito solo se WINNT è definito, quindi lo strumento dovrebbe sapere quali direttive sono generate dal compilatore stesso e quali sono specificate nel comando di compilazione piuttosto che in un file di intestazione.

Come Timmermans, non ho familiarità con nessuno strumento per questo. Ma ho conosciuto programmatori che hanno scritto uno script Perl (o Python) per provare a commentare ogni riga inclusa una alla volta e quindi compilare ogni file.


Sembra che ora Eric Raymond abbia uno strumento per questo .

Cpplint.py di Google ha una regola “include quello che usi” (tra molti altri), ma per quanto posso dire, no “include solo ciò che usi”. Anche così, può essere utile.

Se sei interessato a questo argomento in generale, ti consigliamo di dare un’occhiata alla progettazione del software C ++ su larga scala di Lakos. È un po ‘datato, ma entra in molti problemi di “progettazione fisica” come trovare il minimo assoluto di intestazioni che devono essere incluse. Non ho mai visto questo genere di cose discusse da nessun’altra parte.

Se generalmente i tuoi file di intestazione iniziano

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

(invece di usare #pragma una volta) puoi cambiarlo in:

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

E poiché il compilatore emette il nome del file cpp in fase di compilazione, ciò ti consente di sapere almeno quale file cpp sta causando l’intestazione in più volte.

Dare una prova a Includi Manager . Si integra facilmente in Visual Studio e visualizza i percorsi di inclusione che ti aiutano a trovare cose non necessarie. Internamente utilizza Graphviz ma ci sono molte altre funzionalità interessanti. E sebbene sia un prodotto commerciale ha un prezzo molto basso.

È ansible creare un grafico di inclusione utilizzando C / C ++ Includi dipendenze Dipendenze file e trovare visioni non necessarie.

PC-Lint può davvero farlo. Un modo semplice per farlo è configurarlo per rilevare solo i file di inclusione inutilizzati e ignorare tutti gli altri problemi. Questo è abbastanza semplice: per abilitare solo il messaggio 766 (“File di intestazione non utilizzato nel modulo”), basta includere le opzioni -w0 + e766 nella riga di comando.

Lo stesso approccio può essere utilizzato anche con messaggi correlati come 964 (“File di intestazione non direttamente utilizzato nel modulo”) e 966 (“File di intestazione incluso indirettamente non utilizzato nel modulo”).

FWIW Ho scritto su questo argomento in modo più dettagliato in un post sul blog la scorsa settimana su http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318 .

Se stai cercando di rimuovere i file #include non necessari al fine di ridurre i tempi di costruzione, il tuo tempo e denaro potrebbero essere spesi meglio parallelizzando il tuo processo di compilazione usando cl.exe / MP , make -j , Xoreax IncrediBuild , distcc / icecream , ecc.

Naturalmente, se si dispone già di un processo di sviluppo parallelo e si sta ancora cercando di accelerarlo, è quindi necessario eliminare le direttive #include e rimuovere quelle dipendenze non necessarie.

Inizia con ciascun file di inclusione e assicurati che ogni file include includa solo ciò che è necessario per compilare se stesso. Tutti i file di inclusione che risultano mancanti per i file C ++ possono essere aggiunti ai file C ++ stessi.

Per ciascun file di inclusione e di origine, commentare ogni file include uno alla volta e vedere se viene compilato.

È anche consigliabile ordinare i file include in ordine alfabetico e, laddove ciò non sia ansible, aggiungere un commento.

L’aggiunta di uno o entrambi i seguenti #defines escluderà spesso i file di intestazione non necessari e potrebbe migliorare notevolmente i tempi di compilazione, specialmente se il codice non utilizza le funzioni dell’API di Windows.

 #define WIN32_LEAN_AND_MEAN #define VC_EXTRALEAN 

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

Se non lo sei già, utilizzando un’intestazione precompilata per includere tutto ciò che non cambierai (intestazioni della piattaforma, intestazioni SDK esterne o parti statiche già completate del tuo progetto) farà un’enorme differenza nei tempi di costruzione.

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

Inoltre, anche se potrebbe essere troppo tardi per il tuo progetto, organizzare il tuo progetto in sezioni e non raggruppare tutte le intestazioni locali in un’unica grande intestazione principale è una buona pratica, anche se richiede un piccolo lavoro in più.

Se lavorassi con Eclipse CDT, potresti provare http://includator.com per ottimizzare la tua struttura di inclusione. Tuttavia, Includator potrebbe non sapere abbastanza sugli inclusi predefiniti di VC ++ e impostare CDT per utilizzare VC ++ con gli include corretti non ancora incorporati in CDT.

L’ultimo IDE di Jetbrains, CLion, mostra automaticamente (in grigio) gli include che non sono usati nel file corrente.

È anche ansible avere l’elenco di tutti gli include inutilizzati (e anche funzioni, metodi, ecc …) dall’IDE.

Alcune delle risposte esistenti affermano che è difficile. Questo è vero, perché è necessario un compilatore completo per rilevare i casi in cui una dichiarazione anticipata sarebbe appropriata. Non puoi analizzare C ++ senza sapere cosa significano i simboli; la grammatica è semplicemente troppo ambigua per questo. Devi sapere se un certo nome nomina una class (potrebbe essere dichiarata in avanti) o una variabile (imansible). Inoltre, devi essere attento allo spazio dei nomi.

Forse un po ‘tardi, ma una volta ho trovato uno script perl WebKit che ha fatto esattamente quello che volevi. Avrò bisogno di un adattamento, credo (non sono esperto di perl), ma dovrebbe fare il trucco:

http://trac.webkit.org/browser/branches/old/safari-3-2-branch/WebKitTools/Scripts/find-extra-includes

(questo è un vecchio ramo perché il tronco non ha più il file)

Se c’è un’intestazione particolare che pensi non sia più necessaria (ad esempio string.h), puoi commentare che includi e inserisci qui sotto tutti gli include:

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

Ovviamente le intestazioni dell’interfaccia potrebbero utilizzare una diversa convenzione #define per registrare la loro inclusione nella memoria CPP. O nessuna convenzione, nel qual caso questo approccio non funzionerà.

Quindi ribuild. Ci sono tre possibilità:

  • Costruisce bene. string.h non era critico per la compilazione e l’inclusione per esso può essere rimossa.

  • I viaggi di #error. string.g è stato incluso indirettamente in qualche modo Non si sa ancora se string.h è richiesto. Se è necessario, dovresti includerlo #include (vedi sotto).

  • Ottieni qualche altro errore di compilazione. string.h era necessario e non viene incluso indirettamente, quindi l’inclusione era corretta per cominciare.

Nota che a seconda dell’inclusione indiretta quando il tuo .h o .c usa direttamente un altro .h è quasi certamente un bug: in effetti stai promettendo che il tuo codice richiederà solo quell’intestazione finché un altro header che stai usando lo richiede, che probabilmente non è quello che intendevi.

Le avvertenze citate in altre risposte sulle intestazioni che modificano il comportamento piuttosto che la dichiarazione delle cose che causano errori di compilazione si applicano anche qui.