Raz na jakiś czas kod C ++ nie będzie działał, gdy zostanie skompilowany z pewnym poziomem optymalizacji. Może to być kompilator dokonujący optymalizacji, który łamie kod, lub może to być kod zawierający niezdefiniowane zachowanie, które pozwala kompilatorowi robić to, co czuje.
Załóżmy, że mam fragment kodu, który się psuje, gdy jest kompilowany tylko z wyższym poziomem optymalizacji. Skąd mam wiedzieć, czy to kod, czy kompilator i co mam zrobić, jeśli jest to kompilator?
optimization
compiler
sharptooth
źródło
źródło
Odpowiedzi:
Powiedziałbym, że jest to bezpieczny zakład, że w zdecydowanej większości przypadków uszkodzony jest twój kod, a nie kompilator. I nawet w wyjątkowym przypadku, gdy jest to kompilator, prawdopodobnie używasz jakiejś niejasnej funkcji językowej w niecodzienny sposób, dla której ten kompilator nie jest przygotowany; innymi słowy, najprawdopodobniej możesz zmienić kod na bardziej idiomatyczny i uniknąć słabego punktu kompilatora.
W każdym razie, jeśli możesz udowodnić , że znalazłeś błąd kompilatora (na podstawie specyfikacji języka), zgłoś go twórcom kompilatora, aby mogli go naprawić przez jakiś czas.
źródło
Tak jak zwykle, jak w przypadku innych błędów: wykonaj kontrolowany eksperyment. Zawęź podejrzany obszar, wyłącz optymalizacje dla wszystkiego innego i zacznij zmieniać optymalizacje zastosowane do tego fragmentu kodu. Gdy uzyskasz 100% odtwarzalność, zacznij zmieniać kod, wprowadzając rzeczy, które mogą złamać pewne optymalizacje (np. Wprowadzić możliwe aliasing wskaźnika, wstawić połączenia zewnętrzne z potencjalnymi skutkami ubocznymi itp.). Pomocne może być również sprawdzenie kodu asemblera w debuggerze.
źródło
Sprawdź wynikowy kod asemblera i sprawdź, czy działa on zgodnie z żądaniami twojego źródła. Pamiętaj, że szanse są bardzo duże, że to naprawdę twój kod zawinił w nieoczywisty sposób.
źródło
W ciągu ponad 30 lat programowania liczba znalezionych przeze mnie błędów oryginalnego kompilatora (generowania kodu) wynosiła tylko ~ 10. Liczba błędów własnych (i innych osób), które znalazłem i naprawiłem w tym samym okresie, jest prawdopodobnie > 10 000. Moja „ogólna zasada” polega zatem na tym, że prawdopodobieństwo wystąpienia dowolnego błędu wynikającego z kompilatora wynosi <0,001.
źródło
Zacząłem pisać komentarz, a potem zdecydowałem, że jest za długi i za bardzo do rzeczy.
Twierdziłbym, że to twój kod jest uszkodzony. W mało prawdopodobnym przypadku, gdy odkryłeś błąd w kompilatorze - powinieneś zgłosić go twórcom kompilatora, ale na tym kończy się różnica.
Rozwiązaniem jest zidentyfikowanie szkodliwego konstruktu i przefaktoryzowanie go w taki sposób, aby działał w ten sam sposób inaczej. To najprawdopodobniej rozwiązałoby problem, niezależnie od tego, czy błąd jest po twojej stronie, czy w kompilatorze.
źródło
źródło
int + int
przepełnienia dokładnie tak, jakby skompilował się do instrukcji sprzętowej ADD. Działa dobrze po kompilacji ze starszą wersją GCC, ale nie po kompilacji z nowszym kompilatorem. Najwyraźniej mili ludzie z GCC zdecydowali, że skoro wynik przepełnienia liczb całkowitych jest nieokreślony, ich optymalizator może działać przy założeniu, że to się nigdy nie zdarza. Zoptymalizował ważną gałąź bezpośrednio z kodu.Jeśli chcesz wiedzieć, czy jest to twój kod, czy kompilator, musisz doskonale znać specyfikację C ++.
Jeśli wątpliwości się utrzymują, musisz doskonale znać zestawienie x86.
Jeśli nie masz ochoty uczyć się zarówno do perfekcji, to prawie na pewno jest to nieokreślone zachowanie, które kompilator rozwiązuje inaczej w zależności od poziomu optymalizacji.
źródło
Otrzymanie błędu kompilacji standardowego kodu lub wewnętrznego błędu kompilacji jest bardziej prawdopodobne niż błędy optymalizatorów. Ale słyszałem o kompilatorach optymalizujących pętle niepoprawnie zapominających o niektórych skutkach ubocznych spowodowanych przez metodę.
Nie mam sugestii, jak się dowiedzieć, czy to ty, czy kompilator. Możesz wypróbować inny kompilator.
Pewnego dnia zastanawiałem się, czy to mój kod, czy nie i ktoś zasugerował mi valgrind. Spędziłem 5 lub 10 minut, aby uruchomić z nim mój program (myślę,
valgrind --leak-check=yes myprog arg1 arg2
że to zrobiłem, ale grałem z innymi opcjami) i od razu pokazało mi JEDNĄ linię uruchomioną w jednym konkretnym przypadku, który był problemem. Potem moja aplikacja działała płynnie, bez dziwnych awarii, błędów i dziwnego zachowania. valgrind lub inne podobne narzędzie to dobry sposób na sprawdzenie, czy jest to Twój kod.Uwaga dodatkowa: Kiedyś zastanawiałem się, dlaczego wydajność mojej aplikacji jest do dupy. Okazało się, że wszystkie moje problemy z wydajnością były w jednej linii. Pisałem
for(int i=0; i<strlen(sz); ++i) {
. Sz było kilka MB. Z jakiegoś powodu kompilator działał strlen za każdym razem, nawet po optymalizacji. Jedna linia może być wielką sprawą. Od występów po awarieźródło
Coraz częstszą sytuacją jest to, że kompilatory łamią kod napisany dla dialektów języka C, które wspierały zachowania nie wymagane przez standard, i pozwalały, by kod kierujący te dialekty był bardziej wydajny niż kod ściśle zgodny. W takim przypadku niesprawiedliwe byłoby opisanie jako „zepsuty” kod, który byłby w 100% wiarygodny w kompilatorach, które zaimplementowały dialekt docelowy, lub opisanie jako „zepsuty” kompilator, który przetwarza dialekt, który nie obsługuje wymaganej semantyki . Zamiast tego problemy wynikają po prostu z faktu, że język przetwarzany przez nowoczesne kompilatory z włączonymi optymalizacjami odbiega od dialektów, które były popularne (i nadal są przetwarzane przez wiele kompilatorów z wyłączonymi optymalizacjami lub przez niektóre nawet z włączonymi optymalizacjami).
Na przykład wiele kodu jest napisanych dla dialektów, które uznają za uzasadnione wiele wzorców aliasingu wskaźników, które nie są wymagane przez interpretację standardu przez gcc, i wykorzystuje takie wzorce, aby umożliwić proste tłumaczenie kodu, aby było bardziej czytelne i wydajne byłoby to możliwe przy interpretacji standardu C przez gcc. Taki kod może nie być zgodny z gcc, ale nie oznacza to, że jest uszkodzony. Po prostu polega na rozszerzeniach, które gcc obsługuje tylko przy wyłączonych optymalizacjach.
źródło
Wyodrębnij problematyczne miejsce i porównaj zaobserwowane zachowanie z tym, co powinno się stać zgodnie ze specyfikacją języka. Zdecydowanie nie jest to łatwe, ale właśnie to musisz zrobić, aby wiedzieć (i nie tylko zakładać ).
Prawdopodobnie nie byłbym tak drobiazgowy. Chciałbym raczej zapytać forum pomocy / listę mailingową producenta kompilatora. Jeśli to naprawdę błąd w kompilatorze, mogą go naprawić. Prawdopodobnie i tak byłby to mój kod. Na przykład specyfikacje języka dotyczące widoczności pamięci w wątkach mogą być sprzeczne z intuicją i mogą stać się widoczne tylko przy użyciu określonych flag optymalizacji na określonym sprzęcie (!). Niektóre zachowanie może być niezdefiniowane przez specyfikację, więc może działać z niektórymi kompilatorami / niektórymi flagami, a nie działać z innymi, itp.
źródło
Najprawdopodobniej twój kod ma pewne niezdefiniowane zachowanie (jak wyjaśnili inni, znacznie częściej masz błędy w kodzie niż w kompilatorze, nawet jeśli kompilatory C ++ są tak złożone, że mają błędy; nawet w specyfikacji C ++ występują błędy projektowe) . UB może być tutaj, nawet jeśli skompilowany plik wykonywalny działa (pech).
Powinieneś więc przeczytać blog Lattnera Co każdy programista C powinien wiedzieć o nieokreślonym zachowaniu (większość dotyczy również C ++ 11).
Valgrind narzędzie, a ostatnie
-fsanitize=
opcje oprzyrządowanie do GCC (lub Clang / LLVM ), powinny być również pomocne. I oczywiście włącz wszystkie ostrzeżenia:g++ -Wall -Wextra
źródło