Skąd mam wiedzieć, które części kodu nigdy nie są używane?

312

Mam starszy kod C ++, z którego powinienem usunąć nieużywany kod. Problem polega na tym, że podstawa kodu jest duża.

Jak mogę dowiedzieć się, który kod nigdy nie jest wywoływany / nigdy nie używany?

użytkownik63898
źródło
4
Myślę, że język zapytań o kod daje lepszy wgląd w cały projekt. Nie jestem pewien co do świata c ++, ale wydaje się, że istnieje cppdepend.com (nie jest to darmowy), który wygląda dość przyzwoicie. Może coś takiego może być dostępne za darmo. Inną rzeczą jest to, że przed dokonaniem jakiegokolwiek refaktoryzacji, rozsądnym rozwiązaniem byłyby testy jednostkowe, jeśli nie masz tego teraz. Dzięki testom jednostkowym możesz użyć narzędzi pokrycia kodu do profilowania kodu, który w nim pomoże usunąć martwy kod, jeśli nie możesz go zakryć.
Biswanath 27.01.11
3
Sprawdź referencję tutaj: en.wikipedia.org/wiki/Unreachable_code
Martin York
6
Znajduję podobny temat. stackoverflow.com/questions/229069/…
UmmaGumma
3
Tak, jedną z zabawnych rzeczy w C ++ jest to, że usuwanie „nieużywanych” funkcji może nadal zmieniać wynik programu.
MSalters
1
@MSalters: To ciekawe ... Aby tak się stało, musielibyśmy porozmawiać o tym, która funkcja w zestawie przeciążenia jest wybrana dla danego wywołania, prawda? Według mojej wiedzy, jeśli istnieją dwie funkcje o nazwanych nazwach f(), a wezwanie do f()jednoznacznego rozstrzygnięcia na 1., to nie jest możliwe wykonanie tego wywołania na 2. przez dodanie trzeciej funkcji o nazwie f()- „najgorsze, co możesz zrobić „przez dodanie tej trzeciej funkcji powoduje, że wywołanie staje się niejednoznaczne, a tym samym uniemożliwia kompilację programu. Chciałbym (= być przerażony) zobaczyć kontrprzykład.
j_random_hacker

Odpowiedzi:

197

Istnieją dwie odmiany nieużywanego kodu:

  • lokalny, to znaczy w niektórych funkcjach niektóre ścieżki lub zmienne są nieużywane (lub używane, ale w żaden znaczący sposób, jak napisane, ale nigdy nie czytane)
  • globalny: funkcje, które nigdy nie są wywoływane, obiekty globalne, do których nigdy nie ma dostępu

Po pierwsze, dobry kompilator może pomóc:

  • -Wunused(GCC, Clang ) powinien ostrzegać o nieużywanych zmiennych, nieużywany analizator Clanga został nawet zwiększony, aby ostrzegać o zmiennych, które nigdy nie są odczytywane (nawet jeśli są używane).
  • -Wunreachable-code(starszy GCC, usunięty w 2010 r. ) powinien ostrzegać o lokalnych blokach, do których nigdy nie uzyskuje się dostępu (dzieje się tak w przypadku wczesnych zwrotów lub warunków, które zawsze mają wartość true)
  • nie znam żadnej opcji ostrzegania przed nieużywanymi catchblokami, ponieważ kompilator ogólnie nie może udowodnić, że nie zostanie zgłoszony żaden wyjątek.

W przypadku drugiego rodzaju jest to o wiele trudniejsze. Statycznie wymaga to analizy całego programu i chociaż optymalizacja czasu łącza może faktycznie usunąć martwy kod, w praktyce program został tak przekształcony w momencie wykonywania, że ​​przekazanie znaczących informacji użytkownikowi jest prawie niemożliwe.

Istnieją zatem dwa podejścia:

  • Teoretycznym jest użycie analizatora statycznego. Oprogramowanie, które zbada cały kod jednocześnie bardzo szczegółowo i znajdzie wszystkie ścieżki przepływu. W praktyce nie znam żadnego, który by tu działał.
  • Pragmatycznym jest użycie heurystyki: skorzystaj z narzędzia do pokrycia kodu (w łańcuchu GNU to gcov. Zauważ, że podczas kompilacji należy przekazać określone flagi, aby działało poprawnie). Uruchamiasz narzędzie pokrycia kodu z dobrym zestawem różnych danych wejściowych (testy jednostkowe lub testy nie-regresyjne), martwy kod musi koniecznie znajdować się w nieosiągalnym kodzie ... i możesz zacząć od tego miejsca.

Jeśli jesteś bardzo zainteresowany tym tematem i masz czas i ochotę na samodzielne opracowanie narzędzia, sugeruję użycie bibliotek Clanga do zbudowania takiego narzędzia.

  1. Użyj biblioteki Clanga, aby uzyskać AST (abstrakcyjne drzewo składniowe)
  2. Wykonaj analizę mark-and-sweep od punktów początkowych

Ponieważ Clang przeanalizuje kod za Ciebie i wykona rozwiązywanie problemu przeciążenia, nie będziesz musiał radzić sobie z regułami języków C ++ i będziesz mógł skoncentrować się na problemie.

Jednak tego rodzaju technika nie może zidentyfikować wirtualnych przesłonięć, które nie są używane, ponieważ mogą być wywoływane przez kod innej firmy, o którym nie można się zastanowić.

Matthieu M.
źródło
7
Bardzo fajnie, +1. Podoba mi się, że rozróżniasz kod, który może być statycznie ustalony, aby nigdy nie uruchamiał się w żadnych okolicznościach, i kod, który nie działa w określonym przebiegu, ale potencjalnie może. Myślę, że ta pierwsza jest ważna i jak mówisz, analiza osiągalności z wykorzystaniem AST całego programu jest sposobem na jej uzyskanie. (Zapobieganie foo()oznaczaniu jako „wywołane”, gdy pojawia się tylko w, if (0) { foo(); }byłoby bonusem, ale wymaga dodatkowych sprytów.)
j_random_hacker
@j_random_hacker: być może użycie CFG (Control-Flow Graph) byłoby lepsze teraz, gdy o tym myślę (dzięki Twojemu przykładowi). Wiem, że Clang chętnie wypowiada się na temat porównań tautologicznych, takich jak te, o których wspomniałeś, a zatem używając CFG, możemy być może wcześnie wykryć martwy kod.
Matthieu M.
@ Matthieu: Tak, może CFG też mam na myśli, zamiast AST :) Mam na myśli: digraf, w którym wierzchołki są funkcjami i jest krawędź od funkcji x do funkcji y, ilekroć x mógłby ewentualnie wywołać y. (I z tą ważną właściwością, że przeciążone funkcje są reprezentowane przez odrębne wierzchołki - brzmi to tak, jak Clang robi to za ciebie, uff!)
j_random_hacker
1
@j_random_hacker: CFG jest bardziej skomplikowane niż zwykły digraf, ponieważ reprezentuje cały kod do wykonania w blokach z łączami z jednego bloku do drugiego na podstawie instrukcji warunkowych. Główną zaletą jest to, że w naturalny sposób nadaje się do przycinania kodu, który może być statycznie określony jako martwy (tworzy nieosiągalne bloki, które można zidentyfikować), więc lepiej byłoby wykorzystać CFG niż AST, aby zbudować digraf mówiąc o ... myślę, że :)
Matthieu M.
1
@j_random_hacker: tak naprawdę działa AST Clanga, czyni wszystko jawnym (lub prawie ...), ponieważ jest przeznaczone do pracy z kodem, a nie tylko do kompilacji. Obecnie trwa dyskusja, ponieważ najwyraźniej istnieje problem z listami inicjalizacyjnymi, w którym taka domniemana konwersja nie pojawia się w AST, ale myślę, że zostanie to naprawione.
Matthieu M.
35

W przypadku nieużywanych całych funkcji (i nieużywanych zmiennych globalnych) GCC może faktycznie wykonać większość pracy, pod warunkiem, że używasz GCC i GNU ld.

Podczas kompilacji źródła, użyj -ffunction-sectionsi -fdata-sections, następnie, gdy powiązanie wykorzystania -Wl,--gc-sections,--print-gc-sections. Linker wyświetli teraz listę wszystkich funkcji, które można usunąć, ponieważ nigdy nie zostały one wywołane, i wszystkie globale, do których nigdy się nie odwołano.

(Oczywiście możesz również pominąć --print-gc-sectionsczęść i pozwolić linkerowi na ciche usunięcie funkcji, ale trzymaj je w źródle).

Uwaga: znajdzie to tylko nieużywane pełne funkcje, nie zrobi nic z martwym kodem w obrębie funkcji. Funkcje wywoływane z martwego kodu w funkcjach na żywo również będą przechowywane.

Niektóre funkcje specyficzne dla C ++ również powodują problemy, w szczególności:

  • Funkcje wirtualne. Nie wiedząc, które podklasy istnieją i które są tworzone w czasie wykonywania, nie możesz wiedzieć, jakie funkcje wirtualne potrzebujesz w ostatecznym programie. Linker nie ma wystarczających informacji na ten temat, więc będzie musiał trzymać je wszystkie przy sobie.
  • Globale z konstruktorami i ich konstruktorami. Ogólnie rzecz biorąc, linker nie może wiedzieć, że konstruktor globalny nie ma skutków ubocznych, więc musi go uruchomić. Oczywiście oznacza to, że sama globalność również musi zostać zachowana.

W obu przypadkach wszystko, co jest używane przez funkcję wirtualną lub konstruktora zmiennej globalnej, również musi być przechowywane.

Dodatkowym zastrzeżeniem jest to, że jeśli budujesz bibliotekę współdzieloną, ustawienia domyślne w GCC wyeksportują każdą funkcję w bibliotece współdzielonej, powodując, że będzie ona „używana” w odniesieniu do linkera. Aby to naprawić, musisz ustawić domyślne ukrywanie symboli zamiast eksportować (używając np. -fvisibility=hidden), A następnie jawnie wybrać eksportowane funkcje, które chcesz wyeksportować.

olsner
źródło
Świetna praktyczna rada. Po prostu otrzymanie listy funkcji, o których wiadomo, że nigdzie ich nie używa (nawet jeśli, jak mówisz, ta lista nie jest kompletna), dostaniesz dużo nisko wiszących owoców.
j_random_hacker
Nie wydaje mi się, żeby to działało w przypadku niezainicjowanych szablonów .
Jakub Klinkovský
25

Cóż, jeśli używasz g ++, możesz użyć tej flagi -Wunused

Według dokumentacji:

Ostrzegaj za każdym razem, gdy zmienna nie jest używana poza deklaracją, za każdym razem, gdy funkcja jest deklarowana jako statyczna, ale nigdy nie zdefiniowana, za każdym razem, gdy etykieta jest deklarowana, ale nie jest używana, i za każdym razem, gdy instrukcja oblicza wynik, który nie jest jawnie używany.

http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html

Edycja : Oto inna przydatna flaga -Wunreachable-code Zgodnie z dokumentacją:

Ta opcja ma ostrzegać, gdy kompilator wykryje, że przynajmniej cały wiersz kodu źródłowego nigdy nie zostanie wykonany, ponieważ niektóre warunki nigdy nie są spełnione lub ponieważ następuje po procedurze, która nigdy nie powraca.

Aktualizacja : Znalazłem podobny temat Wykrywanie martwego kodu w starszym projekcie C / C ++

UmmaGumma
źródło
4
Nie złapie to nagłówków prototypowych funkcji, które nigdy nie są wywoływane. Lub metody klasy publicznej, które nie są wywoływane. Może jedynie sprawdzić, czy zmienne o zasięgu lokalnym są używane w tym zakresie.
Falmarri
@Falmarri Nigdy nie użyłem tej flagi. Próbuję się dowiedzieć, jakie martwe kody mogę znaleźć.
UmmaGumma
-Wunusedostrzega o zmiennych, które są zadeklarowane (lub zadeklarowane i zdefiniowane za jednym razem), ale faktycznie nigdy nie są używane. Nawiasem mówiąc, dość denerwujące ze strażnikami z celownikiem: p W Clang istnieje eksperymentalna implementacja, która ostrzega również przed nieulotnymi zmiennymi, do których są zapisywane, ale których nigdy nie czytają (Ted Kremenek). -Wunreachable-codeostrzega o kodzie wewnątrz funkcji, które nie mogą być osiągnięte, może to być kod znajduje się po throwlub returnrachunku lub kodu w oddziale, który nigdy nie zostanie podjęte (co zdarza się w przypadku tautologicznych porównań) na przykład.
Matthieu M.
18

Myślę, że szukasz narzędzia do pokrycia kodu . Narzędzie do pokrycia kodu przeanalizuje Twój kod w trakcie jego działania i poinformuje Cię, które wiersze kodu zostały wykonane i ile razy, a które nie.

Możesz spróbować dać temu narzędziu do pokrycia kodu open source szansę: TestCocoon - narzędzie do pokrycia kodu dla C / C ++ i C #.

Carlos V.
źródło
7
Kluczem jest tutaj „w trakcie działania” - jeśli twoje dane wejściowe nie wykonują żadnej ścieżki kodu, ścieżka ta nie zostanie rozpoznana jako wykorzystana, prawda?
sharptooth 27.01.11
1
To jest poprawne. Bez uruchomienia kodu nie można ustalić, które linie nie są osiągane. Zastanawiam się, jak trudno będzie skonfigurować niektóre testy jednostkowe, aby emulować kilka normalnych przebiegów.
Carlos V
1
@drhishch Myślę, że większość takiego nieużywanego kodu musi znaleźć linker, a nie kompilator.
UmmaGumma
1
@drhirsch To prawda, że ​​kompilator może zająć się niektórymi kodami, które są nieosiągalne, takie jak funkcje deklarowane, ale nie wywoływane i niektóre oceny zwarć, ale co z kodem zależnym od działania użytkownika lub zmiennych czasu wykonywania?
Carlos V
1
@ golcarcol Ok, mamy funkcję void func()w a.cpp, która jest używana w b.cpp. Jak kompilator może sprawdzić, czy func () jest używany w programie? To praca linkerów.
UmmaGumma
15

Prawdziwa odpowiedź brzmi: nigdy tak naprawdę nie możesz być tego pewien.

Przynajmniej w przypadku nietrywialnych przypadków nie możesz być pewien, że wszystko to otrzymałeś. Rozważ następujące kwestie z artykułu Wikipedii na temat nieosiągalnego kodu :

double x = sqrt(2);
if (x > 5)
{
  doStuff();
}

Jak słusznie zauważa Wikipedia, sprytny kompilator może być w stanie złapać coś takiego. Ale rozważ modyfikację:

int y;
cin >> y;
double x = sqrt((double)y);

if (x != 0 && x < 1)
{
  doStuff();
}

Czy kompilator to złapie? Może. Ale aby to zrobić, będzie musiał zrobić coś więcej niż działać sqrtprzy stałej wartości skalarnej. Będzie musiał dowiedzieć się, że (double)yzawsze będzie liczbą całkowitą (łatwą), a następnie zrozumieć matematyczny zakres sqrtzbioru liczb całkowitych (trudny). Bardzo wyrafinowany kompilator może to zrobić dla sqrtfunkcji lub dla każdej funkcji w mat.h lub dla dowolnej funkcji o ustalonym wejściu, której dziedziny może się dowiedzieć. To staje się bardzo, bardzo złożone, a złożoność jest w zasadzie nieograniczona. Możesz nadal dodawać do swojego kompilatora warstwy wyrafinowania, ale zawsze będzie sposób na przemyślenie jakiegoś kodu, który będzie niedostępny dla dowolnego zestawu danych wejściowych.

Są też zestawy danych wejściowych, które po prostu nigdy nie są wprowadzane. Dane wejściowe, które nie miałyby sensu w prawdziwym życiu, lub zostałyby zablokowane przez logikę sprawdzania poprawności w innym miejscu. Kompilator nie może o nich wiedzieć.

Efektem końcowym jest to, że chociaż narzędzia programowe, o których wspominali inni, są niezwykle przydatne, nigdy nie dowiesz się na pewno, że złapałeś wszystko, chyba że później przejdziesz ręcznie kod. Nawet wtedy nigdy nie będziesz pewny, że niczego nie przegapiłeś.

Jedynym prawdziwym rozwiązaniem, IMHO, jest zachowanie jak największej czujności, wykorzystanie automatyzacji do dyspozycji, refaktoryzacja tam, gdzie to możliwe, i ciągłe poszukiwanie sposobów na ulepszenie kodu. Oczywiście i tak warto to zrobić.

Justin Morgan
źródło
1
Prawda i nie zostawiaj martwego kodu! Jeśli usuniesz funkcję, zabij martwy kod. Pozostawienie go „na wszelki wypadek” powoduje wzdęcie, które (jak już omówiłeś) jest trudne do znalezienia później. Niech kontrola wersji zrobi dla ciebie gromadzenie.
Wyścigi lekkości na orbicie
12

Sam go nie użyłem, ale cppcheck twierdzi, że znajduje nieużywane funkcje. Prawdopodobnie nie rozwiąże to całego problemu, ale może być początkiem.

Pan Shark
źródło
Tak, jest w stanie znaleźć lokalne zmienne i funkcje, do których się nie odwołuje.
Chugaister,
Tak, cppcheck --enable=unusedFunction --language=c++ .aby znaleźć te nieużywane funkcje.
Jason Harris
9

Możesz spróbować użyć PC-lint / FlexeLint firmy Gimple Software . Twierdzi, że

znajdź nieużywane makra, typedef, klasy, członków, deklaracje itp. w całym projekcie

Użyłem go do analizy statycznej i uznałem, że jest bardzo dobry, ale muszę przyznać, że nie użyłem go do znalezienia martwego kodu.

Tony
źródło
5

Moje normalne podejście do znajdowania nieużywanych rzeczy to

  1. upewnij się, że system kompilacji poprawnie obsługuje śledzenie zależności
  2. skonfiguruj drugi monitor z pełnoekranowym oknem terminalu, uruchamiając powtarzające się kompilacje i wyświetlając pierwszy ekran wyników. watch "make 2>&1"ma tendencję do robienia tego na Uniksie.
  3. uruchom operację znajdź i zamień na całym drzewie źródłowym, dodając „//?” na początku każdej linii
  4. napraw pierwszy błąd oznaczony przez kompilator, usuwając „//?” w odpowiednich wierszach.
  5. Powtarzaj, aż nie pozostaną żadne błędy.

Jest to dość długi proces, ale daje dobre wyniki.

Simon Richter
źródło
2
Ma zalety, ale jest bardzo pracochłonne. Musisz także pamiętać o odkomentowaniu wszystkich przeciążeń funkcji w tym samym czasie - jeśli ma zastosowanie więcej niż jedna, odkomentowanie mniej preferowanej może pozwolić na kompilację, ale spowoduje nieprawidłowe działanie programu (i błędne wyobrażenie o tym, funkcje są używane).
j_random_hacker
Odkomentuję tylko deklaracje w pierwszym kroku (wszystkie przeciążenia), a w następnej iteracji sprawdzam, które definicje brakuje; w ten sposób widzę, które przeciążenia są faktycznie używane.
Simon Richter
@ Simon: Co ciekawe w komentarzu do głównego pytania, MSalters zwraca uwagę, że nawet obecność / brak deklaracji dla funkcji, która nigdy nie jest wywoływana, może wpłynąć na to, która z 2 innych funkcji zostanie znaleziona przez rozwiązanie przeciążenia. Trzeba przyznać, że wymaga to bardzo dziwnej i przemyślanej konfiguracji, więc w praktyce nie jest to problem.
j_random_hacker
4

Oznacz tyle funkcji publicznych i zmiennych jako prywatne lub chronione bez powodowania błędu kompilacji, robiąc to, spróbuj również refaktoryzować kod. Ustawiając funkcje na prywatne i do pewnego stopnia chronione, ograniczyłeś obszar wyszukiwania, ponieważ funkcje prywatne można wywoływać tylko z tej samej klasy (chyba że istnieją głupie makra lub inne sztuczki pozwalające obejść ograniczenia dostępu, a jeśli tak, to polecam ci znaleźć nową pracę). O wiele łatwiej jest ustalić, że nie potrzebujesz funkcji prywatnej, ponieważ tylko klasa, nad którą obecnie pracujesz, może wywołać tę funkcję. Ta metoda jest łatwiejsza, jeśli baza kodu ma małe klasy i jest luźno powiązana. Jeśli twoja baza kodu nie ma małych klas lub ma bardzo ścisłe powiązanie, sugeruję oczyszczenie ich w pierwszej kolejności.

Następnie zaznacz wszystkie pozostałe funkcje publiczne i utwórz wykres połączeń, aby dowiedzieć się, jaki jest związek między klasami. Z tego drzewa spróbuj dowiedzieć się, która część gałęzi wygląda, jak można ją przyciąć.

Zaletą tej metody jest to, że możesz to zrobić w podziale na moduły, dzięki czemu możesz łatwo przekazywać swoje nieprzyzwoite bez długiego okresu czasu, gdy masz uszkodzoną bazę kodu.

Lie Ryan
źródło
3

Jeśli korzystasz z Linuksa, możesz zajrzeć do callgrindnarzędzia do analizy programu C / C ++, które jest częścią valgrindpakietu, które zawiera również narzędzia sprawdzające wycieki pamięci i inne błędy pamięci (których również powinieneś używać). Analizuje działającą instancję programu i generuje dane na temat jej wykresu połączeń oraz kosztów wydajności węzłów na wykresie połączeń. Zwykle służy do analizy wydajności, ale tworzy również wykres połączeń dla twoich aplikacji, dzięki czemu możesz zobaczyć, jakie funkcje są wywoływane, a także ich wywołujące.

Jest to oczywiście komplementarne do metod statycznych wymienionych gdzie indziej na stronie i będzie pomocne tylko w celu wyeliminowania całkowicie nieużywanych klas, metod i funkcji - nie pomoże to znaleźć martwego kodu w metodach, które są faktycznie wywoływane.

Adam Higuera
źródło
3

Naprawdę nie użyłem żadnego narzędzia, które robi coś takiego ... Ale, o ile widziałem we wszystkich odpowiedziach, nikt nigdy nie powiedział, że tego problemu nie da się obliczyć.

Co mam przez to na myśli? Ten problem nie może być rozwiązany przez żaden algorytm na komputerze. Twierdzenie to (że taki algorytm nie istnieje) jest następstwem problemu zatrzymania Turinga.

Wszystkie narzędzia, których użyjesz, nie są algorytmami, ale heurystyką (tj. Algorytmami niedokładnymi). Nie podadzą ci dokładnie całego niewykorzystanego kodu.

geekazoid
źródło
1
Myślę, że OP chce głównie znaleźć funkcje, które nie są wywoływane z dowolnego miejsca, co z pewnością nie jest niemożliwe do obliczenia - większość współczesnych linkerów może to zrobić! To tylko kwestia wydobycia tych informacji przy najmniejszym bólu i znoju.
j_random_hacker
Masz rację, nie widziałem ostatniego komentarza do głównego pytania. Nawiasem mówiąc, w kodzie mogą znajdować się funkcje, które nie są faktycznie używane. Tego rodzaju rzeczy mogą nie zostać wykryte.
geekazoid
2

Jednym ze sposobów jest użycie debugera i funkcji kompilatora do wyeliminowania nieużywanego kodu maszynowego podczas kompilacji.

Po wyeliminowaniu kodu maszynowego debugger nie pozwoli na wstawienie breakpojnt w odpowiednim wierszu kodu źródłowego. Więc umieszczasz punkty przerwania wszędzie i uruchamiasz program i sprawdzasz punkty przerwania - te, które są w stanie „żaden kod nie został załadowany dla tego źródła” odpowiadają kodowi wyeliminowanemu - albo ten kod nigdy nie jest wywoływany, albo został wstawiony i musisz wykonać pewne minimum analiza, aby dowiedzieć się, które z tych dwóch się wydarzyło.

Przynajmniej tak to działa w Visual Studio i myślę, że inne zestawy narzędzi też mogą to zrobić.

To dużo pracy, ale myślę, że szybciej niż ręczna analiza całego kodu.

sharptooth
źródło
4
Myślę, że pytanie OP dotyczy tego, jak znaleźć mniejszy, łatwiejszy do zarządzania podzbiór kodu źródłowego, nie tyle upewniając się, że skompilowany plik binarny jest wydajny.
j_random_hacker
@j_random_hacker Dałem to jednak - i okazuje się, że eliminacji kodu można nawet użyć do śledzenia z powrotem do oryginalnego kodu źródłowego.
sharptooth 27.01.11
czy potrzebujesz specjalnych flag kompilatora w Visual Studio, aby to osiągnąć? i czy działa tylko w trybie wydania, czy też będzie działał również w debugowaniu?
Naveen
Co z liniami, które są używane, ale zoptymalizowane przez kompilator?
Itamar Katz
@Naveen: W Visual C ++ 9 musisz włączyć optymalizację i użyć / OPT: ICF
sharptooth
2

CppDepend to komercyjne narzędzie, które potrafi wykrywać nieużywane typy, metody i pola i robić znacznie więcej. Jest dostępny dla systemów Windows i Linux (ale obecnie nie obsługuje 64-bitów) i jest oferowany z 2-tygodniową wersją próbną.

Oświadczenie: Nie pracuję tam, ale posiadam licencję na to narzędzie (a także NDepend , który jest bardziej wydajną alternatywą dla kodu .NET).

Dla tych, którzy są ciekawi, oto przykładowa wbudowana (konfigurowalna) reguła wykrywania martwych metod, napisana w CQLinq :

// <Name>Potentially dead Methods</Name>
warnif count > 0
// Filter procedure for methods that should'nt be considered as dead
let canMethodBeConsideredAsDeadProc = new Func<IMethod, bool>(
    m => !m.IsPublic &&       // Public methods might be used by client applications of your Projects.
         !m.IsEntryPoint &&            // Main() method is not used by-design.
         !m.IsClassConstructor &&      
         !m.IsVirtual &&               // Only check for non virtual method that are not seen as used in IL.
         !(m.IsConstructor &&          // Don't take account of protected ctor that might be call by a derived ctors.
           m.IsProtected) &&
         !m.IsGeneratedByCompiler
)

// Get methods unused
let methodsUnused = 
   from m in JustMyCode.Methods where 
   m.NbMethodsCallingMe == 0 && 
   canMethodBeConsideredAsDeadProc(m)
   select m

// Dead methods = methods used only by unused methods (recursive)
let deadMethodsMetric = methodsUnused.FillIterative(
   methods => // Unique loop, just to let a chance to build the hashset.
              from o in new[] { new object() }
              // Use a hashet to make Intersect calls much faster!
              let hashset = methods.ToHashSet()
              from m in codeBase.Application.Methods.UsedByAny(methods).Except(methods)
              where canMethodBeConsideredAsDeadProc(m) &&
                    // Select methods called only by methods already considered as dead
                    hashset.Intersect(m.MethodsCallingMe).Count() == m.NbMethodsCallingMe
              select m)

from m in JustMyCode.Methods.Intersect(deadMethodsMetric.DefinitionDomain)
select new { m, m.MethodsCallingMe, depth = deadMethodsMetric[m] }
Roman Boiko
źródło
Aktualizacja: 64-bitowa obsługa Linuksa została dodana w wersji 3.1.
Roman Boiko,
1

To zależy od platformy, której używasz do tworzenia aplikacji.

Na przykład, jeśli korzystasz z programu Visual Studio, możesz użyć narzędzia, takiego jak .NET ANTS Profiler, który może analizować i profilować kod. W ten sposób powinieneś szybko wiedzieć, która część kodu jest faktycznie używana. Eclipse ma również równoważne wtyczki.

W przeciwnym razie, jeśli chcesz wiedzieć, jaka funkcja aplikacji jest faktycznie używana przez użytkownika końcowego i jeśli możesz ją łatwo zwolnić, możesz użyć pliku dziennika do kontroli.

Dla każdej głównej funkcji możesz prześledzić jej użycie, a po kilku dniach / tygodniach po prostu pobierz ten plik dziennika i spójrz na niego.

AUS
źródło
1
.net ANTS Profiler wygląda tak, jakby był dla C # - czy na pewno działa również dla C ++?
j_random_hacker 27.01.11
@j_random_hacker: o ile wiem, działa z kodem zarządzanym. Tak więc .NET ANTS z pewnością nie będzie w stanie analizować „standardowego” kodu C ++ (tj. Skompilowanego z gcc, ...).
AUS
0

Nie sądzę, że można to zrobić automatycznie.

Nawet w przypadku narzędzi do pokrywania kodu konieczne jest zapewnienie wystarczających danych wejściowych do uruchomienia.

Może to być bardzo złożone i pomocne może być drogie narzędzie do analizy statycznej, takie jak z kompilatora Coverity lub LLVM .

Ale nie jestem pewien i wolałbym ręczną weryfikację kodu.

AKTUALIZACJA

No cóż ... tylko usuwanie nieużywanych zmiennych, nieużywane funkcje nie są trudne.

AKTUALIZACJA

Po przeczytaniu innych odpowiedzi i komentarzy jestem bardziej przekonany, że nie da się tego zrobić.

Musisz znać kod, aby mieć znaczącą miarę pokrycia kodu, a jeśli wiesz, że taka ręczna edycja będzie szybsza niż przygotowanie / uruchomienie / przegląd wyników pokrycia.

9dan
źródło
2
treść twojej odpowiedzi jest myląca, w LLVM nie ma nic cennego ... to nic nie kosztuje!
Matthieu M.
edycja ręczna nie pomoże ci w zmiennych wykonawczych, które przechodzą przez gałęzie logiczne w twoim programie. Co jeśli Twój kod nigdy nie spełnia określonych kryteriów i dlatego zawsze podąża tą samą ścieżką?
Carlos V
0

Miałem dzisiaj znajomego, który zadał mi to pytanie, i rozejrzałem się po obiecujących rozwiązaniach Clanga, np. ASTMatcher i Static Analyzer, które mogą mieć wystarczającą widoczność podczas kompilacji, aby określić martwe sekcje kodu, ale potem Znajdź to:

https://blog.flameeyes.eu/2008/01/today-how-to-identify-unused-exported-functions-and-variables

To prawie kompletny opis tego, jak używać kilku flag GCC, które pozornie zostały zaprojektowane w celu identyfikacji symboli, do których się nie odwołuje!

Steven Lu
źródło
0

Ogólny problem wywołania jakiejś funkcji to NP-Complete. Nie możesz z góry ogólnie wiedzieć, czy jakaś funkcja zostanie wywołana, ponieważ nie będziesz wiedział, czy maszyna Turinga kiedykolwiek się zatrzyma. Możesz dostać, jeśli istnieje ścieżka (statyczna), która przechodzi z funkcji main () do napisanej przez Ciebie funkcji, ale nie gwarantuje to, że zostanie ona kiedykolwiek wywołana.

Luis Colorado
źródło
-3

Cóż, jeśli używasz g ++, możesz użyć tej flagi -Wunused

Według dokumentacji:

Warn whenever a variable is unused aside from its declaration, whenever a function is declared static but never defined, whenever a label is declared but not used, and whenever a statement computes a result that is explicitly not used.

http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html

Edycja: Oto inna przydatna flaga -Wunreachable-code Zgodnie z dokumentacją:

This option is intended to warn when the compiler detects that at least a whole line of source code will never be executed, because some condition is never satisfied or because it is after a procedure that never returns.
ram singh
źródło
6
Ta dokładna informacja została już wspomniana w obecnie najlepiej ocenianej odpowiedzi. Przeczytaj istniejące odpowiedzi, aby uniknąć niepotrzebnego powielania.
j_random_hacker 27.01.11
1
Teraz możesz zdobyć odznakę Peer Pressure!
Andrew Grimm