Jest to dość standard w inżynierii oprogramowania jako całości - podczas optymalizacji kodu kompilator może zmieniać aranżację elementów w dowolny sposób, pod warunkiem, że nie zauważysz żadnej różnicy w działaniu. Na przykład, jeśli inicjujesz zmienną w każdej iteracji pętli i nigdy nie zmieniasz zmiennej w pętli, optymalizator może przenieść tę inicjalizację poza pętlę, abyś nie marnował z nią czasu.
Może również zdawać sobie sprawę, że obliczasz liczbę, z którą nie robisz nic przed nadpisaniem. W takim przypadku może to wyeliminować niepotrzebne obliczenia.
Problem z optymalizacją polega na tym, że będziesz chciał umieścić punkt przerwania na jakimś fragmencie kodu, który optymalizator przesunął lub wyeliminował. W takim przypadku debugger nie może zrobić tego, co chcesz (generalnie umieści punkt przerwania gdzieś blisko). Aby wygenerowany kod był bardziej podobny do tego, co napisałeś, wyłączasz optymalizacje podczas debugowania - gwarantuje to, że kod, który chcesz złamać, naprawdę istnieje.
Musisz jednak zachować ostrożność, ponieważ w zależności od kodu optymalizacja może popsuć! Zasadniczo kod, który jest łamany przez poprawnie działający optymalizator, to po prostu błędny kod, który coś uchodzi na sucho, więc zwykle chcesz dowiedzieć się, dlaczego optymalizator go psuje.
Wysłałem to pytanie do Jacka Ganssle'a, na co odpowiedział mi:
źródło
Zależy, i to ogólnie dotyczy wszystkich narzędzi, nie tylko C30.
Optymalizacje często usuwają i / lub zmieniają kod na różne sposoby. Instrukcja switch może zostać ponownie zaimplementowana za pomocą konstrukcji if / else lub w niektórych przypadkach może zostać usunięta razem. y = x * 16 może zostać zastąpione szeregiem przesunięć w lewo itp., chociaż ten ostatni rodzaj optymalizacji można zwykle przeforsować, przeważnie jest to zmiana instrukcji sterującej.
Może to uniemożliwić przejście debuggera przez kod C, ponieważ struktury zdefiniowane w C już nie istnieją, zostały zastąpione lub ponownie uporządkowane przez kompilator w coś, co zdaniem kompilatora będzie szybsze lub zajmie mniej miejsca. Może również uniemożliwić ustawienie punktów przerwania z listy C, ponieważ instrukcja, na której się łamałeś, może już nie istnieć. Na przykład możesz spróbować ustawić punkt przerwania w instrukcji if, ale kompilator mógł go usunąć. Możesz spróbować ustawić punkt przerwania w pętli forsa lub for, ale kompilator postanowił rozwinąć tę pętlę, aby już nie istniała.
Z tego powodu, jeśli możesz debugować przy wyłączonych optymalizacjach, zwykle jest to łatwiejsze. Zawsze powinieneś ponownie testować z włączonymi optymalizacjami. Jest to jedyny sposób, w jaki dowiesz się, że przegapiłeś coś ważnego
volatile
i powoduje to sporadyczne awarie (lub inną dziwność).W przypadku programowania wbudowanego należy zachować ostrożność przy optymalizacji. W szczególności w częściach kodu, które mają krytyczne znaczenie dla czasu, na przykład niektóre przerywają. W takich przypadkach należy albo zakodować krytyczne bity w zestawie, albo skorzystać z dyrektyw kompilatora, aby upewnić się, że te sekcje nie są zoptymalizowane, aby wiedzieć, że mają one ustalony czas wykonania lub ustalony najgorszy czas działania.
Innym gotcha może być dopasowanie kodu do sterownika, możesz potrzebować optymalizacji gęstości kodu, aby po prostu dopasować kod do układu. Jest to jeden z powodów, dla których zwykle dobrym pomysłem jest zacząć od największej pojemności pamięci ROM uC i wybrać tylko mniejszą do produkcji, po zablokowaniu kodu.
źródło
Generalnie debugowałbym przy użyciu ustawień, które planowałem wydać. Gdybym zamierzał wydać zoptymalizowany kod, debugowałbym przy pomocy zoptymalizowanego kodu. Gdybym zamierzał wydać niezoptymalizowany kod, debugowałbym z niezoptymalizowanym kodem. Robię to z dwóch powodów. Po pierwsze, optymalizatory mogą wprowadzić znaczne różnice czasowe, aby produkt końcowy zachowywał się inaczej niż niezoptymalizowany kod. Po drugie, mimo że większość z nich jest całkiem niezła, producenci kompilatorów popełniają błędy, a zoptymalizowany kod może dawać różne wyniki od niezoptymalizowanego kodu. W rezultacie lubię mieć jak najwięcej czasu na testowanie przy dowolnym ustawieniu, które zamierzam wydać.
Biorąc to pod uwagę, optymalizatory mogą utrudniać debugowanie, jak zauważono w poprzednich odpowiedziach. Jeśli znajdę określoną sekcję kodu, która jest trudna do debugowania, tymczasowo wyłączę optymalizator, przeprowadzę debugowanie, aby kod działał, a następnie ponownie go uruchomię i przetestuję.
źródło
Moją normalną strategią jest rozwijanie z ostateczną optymalizacją (maksimum dla rozmiaru lub prędkości odpowiednio), ale tymczasowo wyłącz optymalizację, jeśli muszę coś debugować lub śledzić. Zmniejsza to ryzyko pojawienia się błędów w wyniku zmiany poziomów optymalizacji.
Typowym trybem awarii jest sytuacja, w której zwiększenie optymalizacji powoduje pojawienie się na powierzchni wcześniej niewidocznych błędów z powodu braku zadeklarowania zmiennych jako zmiennych w razie potrzeby - jest to niezbędne, aby poinformować kompilator, które rzeczy nie powinny być „optymalizowane”.
źródło
Używaj dowolnej formy, którą zamierzasz wydać, debuggerów i kompilacji do debugowania ukryj wiele (DUŻO) błędów, których nie widzisz, dopóki nie skompilujesz do wydania. Do tego czasu znacznie trudniej jest znaleźć te błędy, a nie debugowanie w trakcie pracy. 20 lat temu i nigdy nie miałem zastosowania do gdb lub innego typu debuggera, nie trzeba oglądać zmiennych ani jednego kroku. Setki do tysięcy linii dziennie. Więc jest to możliwe, nie daj się myśleć inaczej.
Kompilacja do debugowania, a następnie kompilacja do wydania może i zajmie dwa razy więcej niż dwa razy więcej wysiłku. Jeśli dostaniesz się do wiązania i będziesz musiał użyć narzędzia takiego jak debugger, to skompiluj, aby debugger przeszedł przez konkretny problem, a następnie powróć do normalnej pracy.
Inne problemy są również prawdziwe, ponieważ optymalizator przyspiesza kod, więc w szczególności w przypadku osadzania zmian czasu z opcjami kompilatora i które mogą wpływać na funkcjonalność programu, ponownie skorzystaj z opcji kompilacji dostarczanej podczas całej fazy. Kompilatory są również programami i zawierają błędy, a optymalizatory popełniają błędy, a niektóre nie wierzą w to. Jeśli tak jest, nie ma nic złego w kompilacji bez optymalizacji, po prostu rób to cały czas. Preferowaną przeze mnie ścieżką jest kompilacja w celu optymalizacji, a jeśli podejrzewam problem z kompilatorem, wyłącz optymalizację, jeśli to naprawia, zwykle w obie strony czasami analizuje dane wyjściowe asemblera, aby dowiedzieć się, dlaczego.
źródło
Zawsze rozwijam kod z -O0 (opcja gcc, aby wyłączyć optymalizację). Kiedy czuję, że jestem w punkcie, w którym chcę zacząć pozwalać, by sprawy zmierzały w kierunku wydania, zacznę od -Os (optymalizacja pod kątem rozmiaru), ponieważ im więcej kodu możesz przechowywać w pamięci podręcznej, tym lepiej, nawet jeśli nie jest zoptymalizowany pod kątem superduperowania.
Uważam, że gdb działa znacznie lepiej z kodem -O0 i jest o wiele łatwiejsze do naśladowania, jeśli musisz wejść do zestawu. Przełączanie między opcjami -O0 i -Os pozwala również zobaczyć, co kompilator robi z twoim kodem. Czasami jest to dość interesująca edukacja, a także może wykrywać błędy kompilatora ... te paskudne rzeczy, które zmuszają cię do wyciągania włosów, próbując dowiedzieć się, co jest nie tak z twoim kodem!
Jeśli naprawdę muszę, zacznę dodawać sekcje -fdata-section i -fcode-section z sekcjami --gc, które pozwalają linkerowi usunąć całe funkcje i segmenty danych, które nie są faktycznie używane. Jest wiele drobiazgów, z którymi możesz majstrować, aby spróbować je zmniejszyć lub przyspieszyć, ale ogólnie rzecz biorąc, są to jedyne sztuczki, których używam, i wszystko, co musi być mniejsze lub szybsze, podam -montować.
źródło
Tak, wyłączanie optymalizacji podczas debugowania jest od dłuższego czasu najlepszą praktyką z trzech powodów:
Wiele osób idzie jeszcze dalej w tym kierunku i wysyła z włączonymi twierdzeniami .
źródło
Proste: optymalizacje są czasochłonne i mogą być bezużyteczne, jeśli trzeba później zmienić ten fragment kodu. Mogą więc być stratą czasu i pieniędzy.
Są one jednak przydatne w gotowych modułach; części kodu, które najprawdopodobniej nie będą już wymagać zmian.
źródło
Z pewnością ma to sens w przypadku punktów przerwania ... ponieważ kompilator może usunąć wiele instrukcji, które nie wpływają na pamięć.
rozważ coś takiego:
może być całkowicie zoptymalizowany (ponieważ
i
nigdy nie jest czytany). z punktu widzenia punktu przerwania wyglądałoby na to, że pominął cały ten kod, gdy w zasadzie go po prostu nie było ... Przypuszczam, że dlatego w funkcjach typu uśpienia często można zobaczyć coś takiego:źródło
Jeśli używasz debugera, wyłączę optymalizacje i włączę debugowanie.
Osobiście uważam, że debugger PIC powoduje więcej problemów niż pomaga mi to naprawić.
Po prostu używam printf () do USART do debugowania moich programów napisanych w C18.
źródło
Większość argumentów przeciwko włączeniu optymalizacji w kompilacji sprowadza się do:
IMHO pierwsze dwa są legalne, trzeci nie tyle. Często oznacza to, że masz zły kod lub polegasz na niebezpiecznym wykorzystaniu języka / implementacji, a może autor jest po prostu fanem dobrego starego Uncle Undefined Behavior.
Blog Embedded in Academia ma coś do powiedzenia na temat nieokreślonego zachowania, a ten post dotyczy wykorzystania kompilatorów: http://blog.regehr.org/archives/761
źródło