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?
c++
optimization
dead-code
użytkownik63898
źródło
źródło
f()
, a wezwanie dof()
jednoznacznego rozstrzygnięcia na 1., to nie jest możliwe wykonanie tego wywołania na 2. przez dodanie trzeciej funkcji o nazwief()
- „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.Odpowiedzi:
Istnieją dwie odmiany nieużywanego kodu:
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)catch
blokami, 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:
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.
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ć.
źródło
foo()
oznaczaniu jako „wywołane”, gdy pojawia się tylko w,if (0) { foo(); }
byłoby bonusem, ale wymaga dodatkowych sprytów.)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-sections
i-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-sections
część 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:
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ć.źródło
Cóż, jeśli używasz g ++, możesz użyć tej flagi
-Wunused
Według dokumentacji:
http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html
Edycja : Oto inna przydatna flaga
-Wunreachable-code
Zgodnie z dokumentacją:Aktualizacja : Znalazłem podobny temat Wykrywanie martwego kodu w starszym projekcie C / C ++
źródło
-Wunused
ostrzega 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-code
ostrzega o kodzie wewnątrz funkcji, które nie mogą być osiągnięte, może to być kod znajduje się pothrow
lubreturn
rachunku lub kodu w oddziale, który nigdy nie zostanie podjęte (co zdarza się w przypadku tautologicznych porównań) na przykład.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 #.
źródło
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.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 :
Jak słusznie zauważa Wikipedia, sprytny kompilator może być w stanie złapać coś takiego. Ale rozważ modyfikację:
Czy kompilator to złapie? Może. Ale aby to zrobić, będzie musiał zrobić coś więcej niż działać
sqrt
przy stałej wartości skalarnej. Będzie musiał dowiedzieć się, że(double)y
zawsze będzie liczbą całkowitą (łatwą), a następnie zrozumieć matematyczny zakressqrt
zbioru liczb całkowitych (trudny). Bardzo wyrafinowany kompilator może to zrobić dlasqrt
funkcji 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ć.
źródło
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.
źródło
cppcheck --enable=unusedFunction --language=c++ .
aby znaleźć te nieużywane funkcje.Możesz spróbować użyć PC-lint / FlexeLint firmy Gimple Software . Twierdzi, że
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.
źródło
Moje normalne podejście do znajdowania nieużywanych rzeczy to
watch "make 2>&1"
ma tendencję do robienia tego na Uniksie.Jest to dość długi proces, ale daje dobre wyniki.
źródło
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.
źródło
Jeśli korzystasz z Linuksa, możesz zajrzeć do
callgrind
narzędzia do analizy programu C / C ++, które jest częściąvalgrind
pakietu, 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.
źródło
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.
źródło
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.
źródło
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 :
źródło
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.
źródło
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.
źródło
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!
źródło
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.
źródło
Cóż, jeśli używasz g ++, możesz użyć tej flagi -Wunused
Według dokumentacji:
http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html
Edycja: Oto inna przydatna flaga -Wunreachable-code Zgodnie z dokumentacją:
źródło