Jak wykryć niepotrzebne pliki #include w dużym projekcie C ++?

96

Pracuję nad dużym projektem C ++ w Visual Studio 2008 i jest tam dużo plików z niepotrzebnymi #includedyrektywami. Czasami #includes są tylko artefaktami i wszystko będzie się dobrze skompilować po ich usunięciu, aw innych przypadkach klasy mogą być zadeklarowane do przodu, a #include można przenieść do .cpppliku. Czy istnieją dobre narzędzia do wykrywania obu tych przypadków?

chaotyczny
źródło

Odpowiedzi:

50

Chociaż nie ujawni niepotrzebnych plików dołączanych, program Visual Studio ma ustawienie /showIncludes(kliknij .cppplik prawym przyciskiem myszy Properties->C/C++->Advanced), które wyświetli drzewo wszystkich dołączonych plików w czasie kompilacji. Może to pomóc w zidentyfikowaniu plików, których nie trzeba uwzględniać.

Możesz również rzucić okiem na idiom pimpl, który pozwoli ci uciec z mniejszą liczbą zależności plików nagłówkowych, aby łatwiej było zobaczyć okrucieństwo, które możesz usunąć.

Zaćmienie
źródło
1
/ showincludes jest świetny. Bez tego robienie tego ręcznie było zniechęcające.
shambolic
30

PC Lint działa całkiem dobrze w tym celu i znajduje dla ciebie również wiele innych głupich problemów. Ma opcje wiersza poleceń, których można użyć do tworzenia narzędzi zewnętrznych w programie Visual Studio, ale odkryłem, że dodatek Visual Lint jest łatwiejszy w użyciu. Pomaga nawet bezpłatna wersja Visual Lint. Ale daj PC-Lint szansę. Skonfigurowanie go tak, aby nie wyświetlał zbyt wielu ostrzeżeń, zajmuje trochę czasu, ale będziesz zaskoczony, co się pojawi.

Joe
źródło
3
Kilka instrukcji, jak to zrobić z PC-lint, można znaleźć na riverblade.co.uk/…
David Sykes
26

!!ZRZECZENIE SIĘ!! Pracuję na komercyjnym narzędziu do analizy statycznej (nie na PC Lint). !!ZRZECZENIE SIĘ!!

Istnieje kilka problemów związanych z prostym podejściem bez analizy:

1) Zestawy przeciążeniowe:

Możliwe, że przeciążona funkcja ma deklaracje pochodzące z różnych plików. Może się zdarzyć, że usunięcie jednego pliku nagłówkowego spowoduje wybranie innego przeciążenia zamiast błędu kompilacji! Rezultatem będzie cicha zmiana semantyki, która może być później bardzo trudna do wyśledzenia.

2) Specjalizacje szablonów:

Podobnie jak w przypadku przeciążenia, jeśli masz częściowe lub jawne specjalizacje dla szablonu, chcesz, aby wszystkie były widoczne, gdy szablon jest używany. Może się zdarzyć, że specjalizacje dla szablonu podstawowego znajdują się w różnych plikach nagłówkowych. Usunięcie nagłówka ze specjalizacją nie spowoduje błędu kompilacji, ale może spowodować niezdefiniowane zachowanie, jeśli ta specjalizacja została wybrana. (Zobacz: Widoczność specjalizacji szablonów funkcji C ++ )

Jak podkreśla „msalters”, przeprowadzenie pełnej analizy kodu pozwala również na analizę wykorzystania klas. Sprawdzając, w jaki sposób klasa jest używana przez określoną ścieżkę plików, możliwe jest, że definicja klasy (a tym samym wszystkie jej zależności) może zostać całkowicie usunięta lub przynajmniej przeniesiona na poziom bliżej głównego źródła w dołączaniu drzewo.

Richard Corden
źródło
@RichardCorden: Twoje oprogramowanie (QA C ++) jest zbyt drogie.
Xander Tulip
13
@XanderTulip: Trudno odpowiedzieć na to pytanie, nie kończąc na prezentacji - dlatego z góry przepraszam. IMHO, musisz wziąć pod uwagę, ile czasu zajęłoby dobremu inżynierowi znalezienie takich rzeczy (a także wielu innych błędów związanych z językiem / przepływem sterowania) w każdym rozsądnym projekcie. Ponieważ oprogramowanie się zmienia, to samo zadanie należy powtarzać wielokrotnie. Więc kiedy obliczasz ilość zaoszczędzonego czasu, koszt narzędzia prawdopodobnie nie jest znaczący.
Richard Corden
10

Nie znam takich narzędzi, myślałem o napisaniu takiego w przeszłości, ale okazuje się, że jest to trudny problem do rozwiązania.

Powiedz, że twój plik źródłowy zawiera ah i bh; ah zawiera #define USE_FEATURE_Xi bh używa #ifdef USE_FEATURE_X. Jeśli #include "a.h"zostanie zakomentowany, plik może nadal się kompilować, ale może nie działać zgodnie z oczekiwaniami. Wykrywanie tego programowo nie jest trywialne.

Jakiekolwiek narzędzie to robi, musi również znać twoje środowisko kompilacji. Jeśli ah wygląda tak:

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

Następnie USE_FEATURE_Xjest definiowana tylko wtedy, gdy WINNTjest zdefiniowana, więc narzędzie musiałoby wiedzieć, jakie dyrektywy są generowane przez sam kompilator, a także które są określone w poleceniu kompilacji, a nie w pliku nagłówkowym.

Graeme Perrow
źródło
9

Podobnie jak Timmermans, nie znam żadnych narzędzi do tego. Ale znam programistów, którzy napisali skrypt w Perlu (lub Pythonie), aby spróbować skomentować każdą linię dołączania po kolei, a następnie skompilować każdy plik.


Wygląda na to, że teraz Eric Raymond ma do tego narzędzie .

Plik cpplint.py Google zawiera regułę „uwzględnij to, czego używasz” (wśród wielu innych), ale o ile wiem, nie ma „uwzględniaj tylko to, czego używasz”. Mimo to może się przydać.

Max Lybbert
źródło
Musiałem się śmiać, kiedy to czytałem. Mój szef zrobił to samo przy jednym z naszych projektów w zeszłym miesiącu. Obniżony nagłówek obejmuje kilka czynników.
Don Wakefield
2
codewarrior na Macu miał wbudowany skrypt, który to robił, komentował, kompilował, po usunięciu komentarza błędu, kontynuuj do końca #includes. To działało tylko w przypadku #includes na początku pliku, ale zwykle tam się znajdują. Nie jest doskonały, ale pozwala zachować rozsądek.
slycrel
5

Jeśli jesteś ogólnie zainteresowany tym tematem, możesz zapoznać się z projektem oprogramowania C ++ firmy Lakos na dużą skalę . Jest trochę przestarzały, ale wiąże się z wieloma problemami z „projektami fizycznymi”, takimi jak znalezienie absolutnego minimum nagłówków, które należy uwzględnić. Tak naprawdę nie widziałem nigdzie indziej dyskusji o tego rodzaju sprawach.

Adrian
źródło
4

Wypróbuj Include Manager . Łatwo integruje się z programem Visual Studio i wizualizuje ścieżki dołączania, co pomaga znaleźć niepotrzebne rzeczy. Wewnętrznie korzysta z Graphviz, ale jest o wiele więcej fajnych funkcji. I choć jest to produkt komercyjny to ma bardzo niską cenę.

Alex
źródło
3

Jeśli twoje pliki nagłówkowe zwykle zaczynają się od

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

(w przeciwieństwie do używania #pragma raz) możesz to zmienić na:

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

A ponieważ kompilator wypisuje nazwę kompilowanego pliku cpp, to pozwoli ci wiedzieć przynajmniej, który plik cpp powoduje wielokrotne wstawianie nagłówka.

Sam
źródło
12
Myślę, że wielokrotne dołączanie nagłówków jest w porządku. Dobrze jest uwzględnić to, czego używasz i nie polegać na plikach dołączanych, aby to zrobić. Myślę, że OP chce znaleźć # zawiera, które nie są w rzeczywistości używane.
Ryan Ginstrom
12
IMO aktywnie źle postępuje. Nagłówki powinny zawierać inne nagłówki, jeśli nie działałyby bez nich. A kiedy trzeba A.hi B.hże zarówno zależą C.hi dołączyć A.hi B.h, ponieważ trzeba mieć, to będzie to C.hdwa razy, ale to dobrze, bo kompilator będzie go pominąć drugi raz, a jeśli nie, to trzeba pamiętać, zawsze włączać C.hwcześniej A.hlub B.hkończyć w znacznie bardziej bezużytecznych inkluzjach.
Jan Hudec
5
Treść jest dokładna, jest to dobre rozwiązanie do wyszukiwania nagłówków, które są dołączane wielokrotnie. Jednak na pierwotne pytanie nie ma odpowiedzi i nie mogę sobie wyobrazić, kiedy byłby to dobry pomysł. Pliki CPP powinny zawierać wszystkie nagłówki, od których zależą, nawet jeśli nagłówek znajduje się wcześniej w innym miejscu. Nie chcesz, aby Twój projekt był kompilowany dla konkretnego zamówienia lub zakładasz, że inny nagłówek będzie zawierał ten, którego potrzebujesz.
jaypb
3

PC-Lint rzeczywiście może to zrobić. Prostym sposobem na to jest skonfigurowanie go tak, aby wykrywał tylko nieużywane pliki dołączania i ignorował wszystkie inne problemy. Jest to całkiem proste - aby włączyć tylko komunikat 766 ("Plik nagłówkowy nie jest używany w module"), wystarczy dołączyć opcje -w0 + e766 w linii poleceń.

To samo podejście można również zastosować w przypadku powiązanych komunikatów, takich jak 964 („Plik nagłówkowy nie jest bezpośrednio używany w module”) i 966 („Plik nagłówkowy dołączany pośrednio nie jest używany w module”).

FWIW Pisałem o tym bardziej szczegółowo w poście na blogu w zeszłym tygodniu pod adresem http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318 .


źródło
2

Jeśli chcesz usunąć niepotrzebne #includepliki, aby skrócić czas kompilacji, Twój czas i pieniądze mogą być lepiej wydane na zrównoleglenie procesu kompilacji za pomocą cl.exe / MP , make -j , Xoreax IncrediBuild , distcc / icecream itp.

Oczywiście, jeśli masz już równoległy proces kompilacji i nadal próbujesz go przyspieszyć, to na wszelki wypadek wyczyść swoje #includedyrektywy i usuń te niepotrzebne zależności.

bk1e
źródło
2

Zacznij od każdego pliku dołączanego i upewnij się, że każdy plik dołączany zawiera tylko to, co jest niezbędne do jego skompilowania. Wszelkie pliki dołączane, których brakuje w plikach C ++, można dodać do samych plików C ++.

Dla każdego pliku dołączania i pliku źródłowego umieść w komentarzu każdy plik dołączany pojedynczo i zobacz, czy się kompiluje.

Dobrym pomysłem jest również sortowanie plików dołączanych alfabetycznie, a jeśli nie jest to możliwe, należy dodać komentarz.

selwyn
źródło
2
Nie jestem pewien, jak praktyczny jest ten komentarz, jeśli chodzi o bardzo dużą liczbę plików implementacji.
Sonny
1

Dodanie jednego lub obu poniższych #defines spowoduje wykluczenie często niepotrzebnych plików nagłówkowych i może znacznie skrócić czas kompilacji, zwłaszcza jeśli kod nie korzysta z funkcji Windows API.

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

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

Roger Nelson
źródło
1
Nie potrzeba obu - VC_EXTRALEAN definiuje WIN32_LEAN_AND_MEAN
Aidan Ryan
1

Jeśli jeszcze tego nie zrobiłeś, użycie wstępnie skompilowanego nagłówka, aby uwzględnić wszystko, czego nie zamierzasz zmieniać (nagłówki platformy, zewnętrzne nagłówki SDK lub statyczne już ukończone elementy projektu), spowoduje ogromną różnicę w czasie kompilacji.

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

Ponadto, chociaż może być już za późno na Twój projekt, podzielenie go na sekcje i nie wrzucanie wszystkich lokalnych nagłówków do jednego dużego nagłówka głównego jest dobrą praktyką, chociaż wymaga trochę dodatkowej pracy.

anon6439
źródło
Świetne wyjaśnienie prekompilowanych nagłówków: cygnus-software.com/papers/precompiledheaders.html (Nie jestem pewien, czy automatyczne generowanie prekompilowanych nagłówków jest zepsute w ostatnich wersjach VisualStudio, ale warto to sprawdzić.)
idbrii
1

Jeśli chciałbyś pracować z Eclipse CDT, możesz wypróbować http://includator.com, aby zoptymalizować strukturę dołączania. Jednak Includator może nie wiedzieć wystarczająco dużo o predefiniowanych dołączeniach VC ++, a konfiguracja CDT do używania VC ++ z poprawnymi dołączeniami nie jest jeszcze wbudowana w CDT.

PeterSom
źródło
1

Najnowsze IDE Jetbrains, CLion, automatycznie wyświetla (na szaro) dołączenia, które nie są używane w bieżącym pliku.

Możliwe jest również posiadanie listy wszystkich nieużywanych włączeń (a także funkcji, metod itp.) Z IDE.

Jean-Michaël Celerier
źródło
0

Niektóre z istniejących odpowiedzi mówią, że jest to trudne. To rzeczywiście prawda, ponieważ potrzebujesz pełnego kompilatora, aby wykryć przypadki, w których deklaracja forward byłaby odpowiednia. Nie możesz analizować C ++, nie wiedząc, co oznaczają symbole; gramatyka jest na to po prostu zbyt niejednoznaczna. Musisz wiedzieć, czy określona nazwa określa klasę (może być zadeklarowana do przodu), czy zmienną (nie można). Ponadto musisz być świadomy przestrzeni nazw.

MSalters
źródło
Możesz po prostu powiedzieć: „Decydowanie, które # zawiera są niezbędne, jest równoznaczne z rozwiązaniem problemu zatrzymania. Powodzenia :)” Oczywiście możesz użyć heurystyki, ale nie znam żadnego wolnego oprogramowania, które to robi.
porge
0

Jeśli istnieje konkretny nagłówek, który Twoim zdaniem nie jest już potrzebny (powiedzmy string.h), możesz skomentować, który zawiera, a następnie umieścić to poniżej wszystkich dołączeń:

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

Oczywiście nagłówki twojego interfejsu mogą używać innej konwencji #define do zapisywania ich włączenia do pamięci CPP. Lub brak konwencji, w takim przypadku to podejście nie zadziała.

Następnie odbuduj. Istnieją trzy możliwości:

  • Buduje się dobrze. string.h nie był krytyczny dla kompilacji i można go usunąć.

  • Wycieczki #error. string.g został w jakiś sposób dołączony pośrednio Nadal nie wiesz, czy string.h jest wymagany. Jeśli jest to wymagane, należy je bezpośrednio # uwzględnić (patrz poniżej).

  • Pojawia się inny błąd kompilacji. string.h był potrzebny i nie jest dołączany pośrednio, więc dołączenie było poprawne na początku.

Zwróć uwagę, że zależność od pośredniego włączenia, gdy .h lub .c bezpośrednio używa innego .h, jest prawie na pewno błędem: w efekcie obiecujesz, że twój kod będzie wymagał tylko tego nagłówka, o ile inny używany nagłówek tego wymaga, co prawdopodobnie nie jest tym, co miałeś na myśli.

Zastrzeżenia wymienione w innych odpowiedziach na temat nagłówków, które modyfikują zachowanie, a nie deklarują rzeczy powodujące awarie kompilacji, mają również zastosowanie tutaj.

Britton Kerin
źródło