Come devo rilevare i file #include non necessari in un progetto C ++ di grandi dimensioni?


96

Sto lavorando a un grande progetto C ++ in Visual Studio 2008 e ci sono molti file con #includedirettive non necessarie . A volte le #includes sono solo artefatti e tutto verrà compilato correttamente con la rimozione, e in altri casi le classi potrebbero essere dichiarate in avanti e #include potrebbe essere spostato nel .cppfile. Esistono buoni strumenti per rilevare entrambi questi casi?

Risposte:


50

Sebbene non rivelerà i file di inclusione non necessari, Visual Studio ha un'impostazione /showIncludes(clic con il tasto destro su un .cppfile Properties->C/C++->Advanced) che produrrà 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 pimpl per farla franca con meno dipendenze del file di intestazione per rendere più facile vedere il cruft che puoi rimuovere.


1
/ showincludes è fantastico. Farlo manualmente era scoraggiante senza quello.
caotico

30

PC Lint funziona abbastanza bene per questo e trova anche tutti i tipi di altri problemi sciocchi per te. Ha opzioni della riga di comando che possono essere utilizzate per creare strumenti esterni in Visual Studio, ma ho scoperto che il componente aggiuntivo Visual Lint è più facile da usare . Anche la versione gratuita di Visual Lint aiuta. Ma dai una possibilità a PC-Lint. Configurarlo in modo da non darti troppi avvisi richiede un po 'di tempo, ma rimarrai stupito di quello che verrà fuori.


3
Alcune istruzioni su come eseguire questa operazione con pc-lint possono essere trovate su riverblade.co.uk/…
David Sykes,


26

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

Ci sono diversi problemi con un semplice approccio senza analisi:

1) Set di sovraccarico:

È possibile che una funzione sovraccarica abbia dichiarazioni che provengono da file diversi. Potrebbe essere che la rimozione di un file di intestazione comporti la scelta di un sovraccarico diverso piuttosto che 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 tutte visibili quando viene utilizzato il modello. È possibile 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 quella specializzazione sarebbe stata selezionata. (Vedi: Visibilità della specializzazione del modello della funzione C ++ )

Come sottolineato da "msalters", eseguire un'analisi completa del codice consente anche di analizzare l'utilizzo della classe. Controllando come viene usata una classe attraverso uno specifico percorso di file, è possibile che la definizione della classe (e quindi tutte le sue dipendenze) possa essere rimossa completamente o almeno spostata ad un livello più vicino alla sorgente principale nell'inclusione albero.


@RichardCorden: il tuo software (QA C ++) è troppo costoso.
Xander Tulip

13
@XanderTulip: è difficile rispondere a questo senza finire in un discorso di vendita, quindi mi scuso in anticipo. IMHO, quello che devi considerare è quanto tempo impiegherebbe un buon ingegnere per trovare cose come questa (così come molti altri bug di linguaggio / flusso di controllo) in qualsiasi progetto di dimensioni ragionevoli. Quando il software cambia, la stessa attività deve essere ripetuta ancora e ancora. Quindi, quando si calcola la quantità di tempo risparmiata, il costo dello strumento probabilmente non è significativo.
Richard Corden

10

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

Supponiamo che il tuo file sorgente includa ah e bh; ah contiene #define USE_FEATURE_Xe bh usa #ifdef USE_FEATURE_X. Se #include "a.h"è commentato, il tuo file potrebbe ancora essere compilato, ma potrebbe non fare quello che ti aspetti. Rilevarlo programmaticamente non è banale.

Qualunque sia lo strumento che fa questo, dovrebbe conoscere anche il tuo ambiente di costruzione. Se ah sembra:

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

Quindi USE_FEATURE_Xè 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.


9

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 ciascuna riga di inclusione 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 "includi ciò che usi" (tra molte altre), ma per quanto ne so, non "includi solo ciò che usi". Anche così, può essere utile.


Ho dovuto ridere quando ho letto questo. Il mio capo ha fatto proprio questa cosa in uno dei nostri progetti il ​​mese scorso. L'intestazione ridotta include un paio di fattori.
Don Wakefield,

2
codewarrior su Mac aveva uno script integrato per fare questo, commentare, compilare, in caso di errore un-comment, continuare fino alla fine di #includes. Funzionava solo per #include all'inizio di un file, ma di solito è lì che si trovano. Non è perfetto, ma mantiene le cose ragionevolmente sane.
slycrel

5

Se sei interessato a questo argomento in generale, potresti dare un'occhiata alla progettazione del software C ++ su larga scala di Lakos . È un po 'datato, ma si occupa di 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.


4

Dare Includere manager una prova. 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 interessanti funzionalità. E sebbene sia un prodotto commerciale ha un prezzo molto basso.



3

Se i tuoi file di intestazione generalmente iniziano con

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

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

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

E poiché il compilatore restituisce il nome del file cpp che viene compilato, questo ti farebbe sapere almeno quale file cpp sta causando il richiamo dell'intestazione più volte.


12
Penso che vada bene includere le intestazioni più volte. È bene includere ciò che si utilizza e non dipendere dai file di inclusione per farlo. Penso che ciò che l'OP vuole sia trovare #include che non vengono effettivamente utilizzati.
Ryan Ginstrom

12
IMO attivamente cosa sbagliata da fare. Le intestazioni dovrebbero includere altre intestazioni quando non funzionerebbero senza di esse. E quando si ha A.he B.hche entrambi dipendono C.he si include A.he B.h, perché avete bisogno di entrambi, si avrà includere C.hdue volte, ma va bene, perché il compilatore salterà la seconda volta e se non l'avete fatto, che ci si deve ricordare includere sempre C.hprima A.ho B.hfinire in inclusioni molto più inutili.
Jan Hudec

5
Il contenuto è accurato, questa è una buona soluzione per trovare intestazioni incluse più volte. Tuttavia, la domanda originale non trova risposta e non riesco a immaginare quando sarebbe una buona idea. I file Cpp dovrebbero includere tutte le intestazioni da cui dipendono, anche se l'intestazione è inclusa prima da qualche altra parte. Non vuoi che il tuo progetto sia specifico per l'ordine di compilazione o presumi che un'intestazione diversa includa quella che ti serve.
jaypb

3

PC-Lint può davvero farlo. Un modo semplice per farlo è configurarlo per rilevare solo i file include non utilizzati e ignorare tutti gli altri problemi. Questo è abbastanza semplice - per abilitare solo il messaggio 766 ("File di intestazione non utilizzato nel modulo"), includi semplicemente le opzioni -w0 + e766 sulla riga di comando.

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

FWIW Ho scritto su questo 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 .


2

Se stai cercando di rimuovere #includefile non necessari per ridurre i tempi di compilazione, 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.

Ovviamente, se hai già un processo di compilazione parallelo e stai ancora cercando di accelerarlo, ripulisci le tue #includedirettive e rimuovi quelle dipendenze non necessarie.


2

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

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

È anche una buona idea ordinare alfabeticamente i file di inclusione e, se ciò non è possibile, aggiungere un commento.


2
Non sono sicuro di quanto sia pratico questo commento, se è coinvolto un numero molto elevato di file di implementazione.
Sonny

1

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

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

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


1
Non c'è bisogno di entrambi - VC_EXTRALEAN definisce WIN32_LEAN_AND_MEAN
Aidan Ryan,

1

Se non lo sei già, l'utilizzo di 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 compilazione.

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

Inoltre, sebbene possa 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, sebbene richieda un po 'di lavoro extra.


Ottima spiegazione delle intestazioni precompilate: cygnus-software.com/papers/precompiledheaders.html (Non sono sicuro che la generazione automatica di intestazioni precompilate sia interrotta nelle versioni recenti di VisualStudio, ma vale la pena controllare.)
idbrii

1

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


1

L'IDE più recente di Jetbrains, CLion, mostra automaticamente (in grigio) gli include non utilizzati nel file corrente.

È anche possibile avere la lista di tutti gli include non utilizzati (e anche funzioni, metodi, ecc ...) dall'IDE.


0

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


Potresti semplicemente dire "Decidere quali #include sono necessari equivale a risolvere il problema dell'arresto. Buona fortuna :)" Ovviamente puoi usare l'euristica, ma non conosco alcun software libero che lo faccia.
porges


0

Se c'è un'intestazione particolare che ritieni non sia più necessaria (ad esempio string.h), puoi commentare quell'inclusione, quindi metterla 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 ricostruisci. Ci sono tre possibilità:

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

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

  • Ottieni qualche altro errore di compilazione. string.h era necessario e non è stato incluso indirettamente, quindi l'inclusione era corretta all'inizio.

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é qualche altra intestazione che stai usando lo richiede, che probabilmente non è quello che intendevi.

Le avvertenze menzionate in altre risposte sulle intestazioni che modificano il comportamento piuttosto che dichiarare cose che causano errori di compilazione si applicano anche qui.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.