Standard C11 wydaje się sugerować, że instrukcje iteracji ze stałymi wyrażeniami kontrolującymi nie powinny być optymalizowane. Czerpię radę z tej odpowiedzi , która konkretnie cytuje sekcję 6.8.5 projektu standardu:
Instrukcja iteracji, której wyrażenie kontrolne nie jest wyrażeniem stałym ... może zostać przyjęte przez implementację jako zakończona.
W tej odpowiedzi wspomniano, że taka pętla while(1) ;
nie powinna podlegać optymalizacji.
Więc ... dlaczego Clang / LLVM optymalizuje poniższą pętlę (z kompilacją cc -O2 -std=c11 test.c -o test
)?
#include <stdio.h>
static void die() {
while(1)
;
}
int main() {
printf("begin\n");
die();
printf("unreachable\n");
}
Na mojej maszynie drukuje się begin
, a następnie ulega awarii na niedozwolonej instrukcji ( ud2
pułapka umieszczona po die()
). W Godbolt możemy zobaczyć, że nic nie jest generowane po wezwaniu do puts
.
Zaskakująco trudne zadanie sprawiło, że Clang wyprowadził nieskończoną pętlę poniżej -O2
- podczas gdy mogłem wielokrotnie testować volatile
zmienną, która wymaga odczytu pamięci, którego nie chcę. A jeśli zrobię coś takiego:
#include <stdio.h>
static void die() {
while(1)
;
}
int main() {
printf("begin\n");
volatile int x = 1;
if(x)
die();
printf("unreachable\n");
}
... Odbitki Clanga, begin
a po nich unreachable
jakby nieskończona pętla nigdy nie istniała.
Jak sprawić, by Clang wyprowadził prawidłową nieskończoną pętlę bez dostępu do pamięci z włączonymi optymalizacjami?
źródło
exit()
, i ponieważ kod mógł odkryć sytuację, w której nie może zagwarantować, że skutki dalszego wykonywania nie będą gorsze niż bezużyteczne . Pętla przeskakiwania do siebie jest dość kiepskim sposobem radzenia sobie z takimi sytuacjami, ale może jednak być najlepszym sposobem radzenia sobie w złej sytuacji.Odpowiedzi:
Norma C11 mówi 6.8.5 / 6:
Dwie nuty nie są normatywne, ale dostarczają przydatnych informacji:
W twoim przypadku
while(1)
jest to krystalicznie czyste stałe wyrażenie, więc implementacja nie może założyć, że zakończy się. Taka implementacja byłaby beznadziejnie zepsuta, ponieważ pętle „na zawsze” są powszechną konstrukcją programistyczną.O ile wiem, co dzieje się z „nieosiągalnym kodem” po pętli, nie jest dobrze zdefiniowane. Jednak klang rzeczywiście zachowuje się bardzo dziwnie. Porównanie kodu maszynowego z gcc (x86):
gcc 9.2
-O3 -std=c11 -pedantic-errors
klang 9.0.0
-O3 -std=c11 -pedantic-errors
gcc generuje pętlę, clang po prostu wpada do lasu i kończy pracę z błędem 255.
Pochylam się nad tym, że zachowanie klangu jest niezgodne z przeznaczeniem. Ponieważ próbowałem rozszerzyć twój przykład w ten sposób:
Dodałem C11,
_Noreturn
aby pomóc kompilatorowi dalej. Powinno być jasne, że ta funkcja się rozłączy, na podstawie samego słowa kluczowego.setjmp
zwróci 0 po pierwszym uruchomieniu, więc ten program powinien po prostu uderzyć wwhile(1)
i zatrzymać się tam, wypisując tylko „start” (zakładając, że \ n opróżnia standardowe wyjście). Dzieje się tak z gcc.Jeśli pętla została po prostu usunięta, powinna wypisać „rozpocząć” 2 razy, a następnie wydrukować „nieosiągalny”. Jednak w przypadku clang ( godbolt ) wypisuje „ start ” 1 raz, a następnie „nieosiągalny” przed zwróceniem kodu wyjścia 0. To po prostu źle, bez względu na to, jak to określisz.
Nie mogę znaleźć tutaj powodu do twierdzenia o nieokreślonym zachowaniu, więc uważam, że jest to błąd. W każdym razie takie zachowanie sprawia, że clang jest w 100% bezużyteczny dla programów takich jak systemy wbudowane, w których po prostu musisz polegać na wiecznych pętlach zawieszających program (podczas oczekiwania na watchdoga itp.).
źródło
6.8.5/6
ma postać, jeśli (te), możesz założyć (to) . To nie znaczy, że jeśli nie (te), możesz nie założyć (tego) . Jest to specyfikacja tylko wtedy, gdy warunki są spełnione, a nie wtedy, gdy są niespełnione, gdzie możesz zrobić, co chcesz, stosując standardy. A jeśli nie ma żadnych obserwowalnych ...int z=3; int y=2; int x=1; printf("%d %d\n", x, z);
nie ma żadnego2
, więc w pustym znaczeniu bezużytecznymx
nie został przypisany po,y
ale poz
optymalizacji. Wychodząc od ostatniego zdania, przestrzegamy normalnych zasad, zakładamy, że czas został zatrzymany (ponieważ nie byliśmy już lepiej ograniczeni) i pozostawiony w ostatecznym, „nieosiągalnym” druku. Teraz optymalizujemy to bezużyteczne stwierdzenie (ponieważ nie wiemy nic lepszego).while(1);
to samo, coint y = 2;
stwierdzenie pod względem tego, jaką semantykę możemy zoptymalizować, nawet jeśli ich logika pozostaje w źródle. Od roku 1528 miałem wrażenie, że mogą być tacy sami, ale ponieważ ludzie o wiele bardziej doświadczeni niż ja kłócą się w drugą stronę, i najwyraźniej jest to oficjalny błąd, poza filozoficzną debatą na temat tego, czy sformułowanie w normie jest wyraźne , argument jest dyskusyjny.Musisz wstawić wyrażenie, które może powodować efekt uboczny.
Najprostsze rozwiązanie:
Link Godbolt
źródło
asm("")
jest niejawny,asm volatile("");
a zatem instrukcja asm musi być uruchamiana tyle razy, ile dzieje się na abstrakcyjnej maszynie gcc.gnu.org/onlinedocs/gcc/Basic-Asm.html . (Pamiętaj, że nie jest bezpieczne, aby skutki uboczne obejmowały pamięć lub rejestry; potrzebujesz Extended asm z"memory"
clobber, jeśli chcesz czytać lub zapisywać pamięć, do której kiedykolwiek masz dostęp z C. Basic asm jest bezpieczny tylko dla rzeczy takich jakasm("mfence")
lubcli
.)Inne odpowiedzi zawierały już sposoby na to, aby Clang emitował nieskończoną pętlę z wbudowanym językiem asemblera lub innymi efektami ubocznymi. Chcę tylko potwierdzić, że to rzeczywiście błąd kompilatora. W szczególności jest to długotrwały błąd LLVM - stosuje koncepcję C ++ „wszystkie pętle bez efektów ubocznych muszą się kończyć” w językach, w których nie powinien, takich jak C.
Na przykład język programowania Rust pozwala również na nieskończone pętle i wykorzystuje LLVM jako backend i ma ten sam problem.
W krótkim okresie wydaje się, że LLVM będzie nadal zakładać, że „wszystkie pętle bez skutków ubocznych muszą się zakończyć”. Dla każdego języka, który zezwala na nieskończone pętle, LLVM oczekuje, że front-end wstawi
llvm.sideeffect
kody do takich pętli. To właśnie planuje zrobić Rust, więc Clang (podczas kompilacji kodu C) prawdopodobnie będzie musiał to zrobić.źródło
sideeffect
operację (w 2017 r.) I oczekuje, że interfejsy wstawią tę operację w pętle według własnego uznania. LLVM musiał wybrać pewne domyślne ustawienia dla pętli i zdarzyło się wybrać taki, który jest zgodny z zachowaniem C ++, celowo lub w inny sposób. Oczywiście pozostało jeszcze wiele do zrobienia optymalizacji, na przykład połączenie kolejnychsideeffect
operacji w jeden. (To właśnie blokuje korzystanie z frontonu przez Rust.) Na tej podstawie błąd znajduje się w interfejsie (clang), który nie wstawia op w pętle.sideeffect
operacji na początku każdej funkcji i nie widział regresji wydajności środowiska wykonawczego. Jedynym problemem jest kompilacja regresji czasu , najwyraźniej z powodu braku połączenia kolejnych operacji, jak wspomniałem w poprzednim komentarzu.To jest błąd Clanga
... podczas wstawiania funkcji zawierającej nieskończoną pętlę. Zachowanie jest inne, gdy
while(1);
pojawia się bezpośrednio w głównym, który pachnie dla mnie bardzo błędnie.Zobacz odpowiedź @ Arnavion na podsumowanie i linki. Reszta tej odpowiedzi została napisana, zanim otrzymałem potwierdzenie, że był to błąd, a tym bardziej znany błąd.
Aby odpowiedzieć na pytanie tytułowe: Jak utworzyć nieskończoną pustą pętlę, która nie będzie zoptymalizowana? ? -
utworzyć
die()
makro, a nie funkcję , aby obejść ten błąd w Clang 3.9 i nowszych. (Wcześniejsze wersje Clanga albo utrzymywały pętlę, albo wysyłały „a”call
do nieliniowej wersji funkcji z nieskończoną pętlą.) Wydaje się to bezpieczne, nawet jeśliprint;while(1);print;
funkcja włącza się w jej wywołującym ( Godbolt ).-std=gnu11
vs.-std=gnu99
nic nie zmienia.Jeśli zależy ci tylko na GNU C, P__J __
__asm__("");
wewnątrz pętli również działa i nie powinno zaszkodzić optymalizacji żadnego otaczającego kodu dla żadnego kompilatora, który go rozumie. Podstawowe instrukcje asm GNU C Basic są domyślnievolatile
, więc liczy się to jako widoczny efekt uboczny, który musi „wykonać” tyle razy, ile by to miało miejsce w maszynie abstrakcyjnej C. (I tak, Clang implementuje dialekt GNU języka C, jak udokumentowano w instrukcji GCC.)Niektórzy twierdzą, że optymalizacja pustej nieskończonej pętli może być legalna. Nie zgadzam się 1 , ale nawet jeśli to zaakceptujemy, Clang nie może również legalnie zakładać, że instrukcje po osiągnięciu pętli są nieosiągalne, i pozwolić , aby wykonanie spadło z końca funkcji do następnej lub do śmieci który dekoduje jako losowe instrukcje.
(Byłoby to zgodne ze standardami dla Clang ++ (ale nadal niezbyt przydatne); nieskończone pętle bez żadnych skutków ubocznych są UB w C ++, ale nie C.
Jest while (1); niezdefiniowane zachowanie w C? UB pozwala kompilatorowi emitować praktycznie wszystko dla kodu na ścieżce wykonania, która na pewno napotka UB.
asm
Instrukcja w pętli unikałaby tego UB dla C ++. Ale w praktyce Clang kompiluje jako C ++ nie usuwa nieskończonych pustych pętli o stałym wyrażeniu, z wyjątkiem wstawiania, tak jak wtedy, gdy kompilacja jako C.)Ręczne wstawianie
while(1);
zmienia sposób kompilacji Clanga: nieskończona pętla obecna w asm. Tego oczekujemy od prawnika POV.W eksploratorze kompilatorów Godbolt Clang 9.0 -O3 kompiluje się jako C (
-xc
) dla x86-64:Ten sam kompilator z tymi samymi opcjami kompiluje najpierw ten ,
main
który wywołujeinfloop() { while(1); }
ten samputs
, ale potem przestaje wydawać instrukcje dlamain
tego punktu. Tak jak powiedziałem, wykonanie po prostu spada z końca funkcji, do dowolnej funkcji, która będzie następna (ale przy stosie przesuniętym względem wejścia funkcji, więc nie jest to nawet poprawne wywołanie ogona).Prawidłowe opcje to
label: jmp label
nieskończoną pętlęreturn 0
zmain
.Awaria lub kontynuacja bez drukowania „nieosiągalnego” najwyraźniej nie jest odpowiednia dla implementacji C11, chyba że istnieje UB, którego nie zauważyłem.
Przypis 1:
Dla przypomnienia, zgadzam się z odpowiedzią @ Lundin, która przytacza standard dla dowodów, że C11 nie pozwala na założenie zakończenia dla nieskończonych pętli o stałym wyrażeniu, nawet gdy są one puste (brak we / wy, niestabilność, synchronizacja lub inne widoczne efekty uboczne).
Jest to zestaw warunków, które pozwoliłyby skompilować pętlę do pustej pętli asm dla normalnego procesora. (Nawet jeśli treść nie była pusta w źródle, przypisania do zmiennych nie mogą być widoczne dla innych wątków lub procedur obsługi sygnałów bez UB wyścigu danych, gdy pętla jest uruchomiona. Zatem zgodna implementacja mogłaby usunąć takie treści pętli, gdyby chciała To pozostawia pytanie, czy samą pętlę można usunąć. ISO C11 wyraźnie mówi nie.)
Biorąc pod uwagę, że C11 wyróżnia ten przypadek jako przypadek, w którym implementacja nie może zakładać, że pętla się kończy (i że nie jest to UB), wydaje się jasne, że zamierzają być obecni w pętli w czasie wykonywania. Implementacja ukierunkowana na procesory z modelem wykonania, który nie może wykonać nieskończonej ilości pracy w skończonym czasie, nie ma uzasadnienia dla usuwania pustej stałej pętli nieskończonej. Lub nawet ogólnie, dokładne sformułowanie dotyczy tego, czy można je „założyć, że wygasną”, czy nie. Jeśli pętla nie może się zakończyć, oznacza to, że późniejszy kod jest nieosiągalny, bez względu na argumenty dotyczące matematyki i nieskończoności oraz czas potrzebny na wykonanie nieskończonej ilości pracy na jakiejś hipotetycznej maszynie.
Co więcej, Clang nie jest jedynie DeathStation 9000 zgodnym z ISO C, ale ma być przydatny do programowania systemów niskopoziomowych w świecie rzeczywistym, w tym do jąder i osadzonych elementów. Niezależnie od tego, czy akceptujesz argumenty dotyczące C11 zezwalające na usunięcie
while(1);
, nie ma sensu, aby Clang chciał to zrobić. Jeśli napiszeszwhile(1);
, to prawdopodobnie nie był wypadek. Usuwanie pętli, które przypadkowo kończą się nieskończonością (z wyrażeniami kontrolnymi zmiennych wykonawczych), może być przydatne i ma to sens dla kompilatorów.Rzadko zdarza się, że chcesz się kręcić do następnego przerwania, ale jeśli napiszesz to w C, to na pewno tego się spodziewasz. (I co dzieje się w GCC i Clang, z wyjątkiem Clanga, gdy nieskończona pętla znajduje się w funkcji otoki).
Na przykład w prymitywnym jądrze systemu operacyjnego, gdy program planujący nie ma zadań do uruchomienia, może uruchomić zadanie bezczynne. Pierwszą implementacją tego może być
while(1);
.Lub w przypadku sprzętu bez funkcji bezczynności oszczędzania energii, może to być jedyna implementacja. (Do wczesnych lat 2000., tak myślę, nie było to rzadkie na x86. Chociaż
hlt
instrukcja istniała, IDK jeśli oszczędzał znaczącą ilość energii, dopóki procesory nie zaczęły mieć stanów bezczynności o niskiej mocy).źródło
-ffreestanding -fno-strict-aliasing
. Działa dobrze z ARM i być może ze starszą AVR.Dla przypomnienia Clang również źle się zachowuje
goto
:Daje takie same wyniki jak w pytaniu, tj .:
Widzę, że nie widzę żadnego sposobu, aby odczytać to jako dozwolone w C11, który mówi tylko:
Ponieważ
goto
nie jest to „oświadczenie iteracja” (6.8.5 listywhile
,do
afor
) nic o specjalnej „ustanie-zakłada” odpusty stosuje się jednak chcesz je przeczytać.Według oryginalnego pytania kompilator Godbolt link to x86-64 Clang 9.0.0 i flagi są
-g -o output.s -mllvm --x86-asm-syntax=intel -S --gcc-toolchain=/opt/compiler-explorer/gcc-9.2.0 -fcolor-diagnostics -fno-crash-diagnostics -O2 -std=c11 example.c
Z innymi, takimi jak x86-64 GCC 9.2, otrzymujesz całkiem nieźle idealny:
Flagi:
-g -o output.s -masm=intel -S -fdiagnostics-color=always -O2 -std=c11 example.c
źródło
nasty: goto nasty
może być zgodny i nie obracać procesora (CPU), dopóki interwencja użytkownika lub zasoby nie zainterweniuje.bar()
wewnątrzfoo()
być przetwarzane jako wezwanie__1foo
do__2bar
od__2foo
do__3bar
, itd. i od__16foo
do__launch_nasal_demons
, co pozwoliłoby na statyczne przydzielenie wszystkich automatycznych obiektów i spowodowałoby, że zwykle limit czasu wykonywania stałby się limitem translacji.Wcielę się w adwokata diabła i argumentuję, że standard nie zabrania kompilatorowi optymalizacji nieskończonej pętli.
Przeanalizujmy to. Można przyjąć, że wypowiedź iteracyjna, która spełnia określone kryteria, kończy się:
Nie mówi to nic o tym, co się stanie, jeśli kryteria nie zostaną spełnione, a zakładanie, że pętla może się zakończyć nawet wtedy nie jest wyraźnie zabronione, o ile przestrzegane są inne reguły standardu.
do { } while(0)
lubwhile(0){}
są przecież instrukcjami iteracyjnymi (pętlami), które nie spełniają kryteriów, które pozwalają kompilatorowi na przyjęcie kaprysu, że kończą, a jednak oczywiście kończą.Ale czy kompilator może po prostu zoptymalizować
while(1){}
?5.1.2.3p4 mówi:
Wspomina o wyrażeniach, a nie o wyrażeniach, więc nie jest w 100% przekonujący, ale z pewnością umożliwia wywołania takie jak:
do pominięcia. Co ciekawe, clang go pomija, a gcc nie .
źródło
while(1){}
nieskończona sekwencja1
ocen przeplata się z{}
ocenami, ale gdzie w normie mówi się, że oceny te muszą zająć niezerowy czas? Sądzę, że zachowanie gcc jest bardziej przydatne, ponieważ nie potrzebujesz sztuczek związanych z dostępem do pamięci lub sztuczek poza językiem. Ale nie jestem przekonany, że standard zabrania tej optymalizacji w clang. Jeśliwhile(1){}
intencją jest nieoptymalizowanie, norma powinna być jednoznacznie na ten temat, a nieskończone zapętlenie powinno być wymienione jako zauważalny efekt uboczny w 5.1.2.3p2.1
warunek jako obliczenie wartości. Czas wykonania nie ma znaczenia - liczy się to, cowhile(A){} B;
może nie być zoptymalizowane z dala całkowicie, nie zoptymalizowaneB;
i nie re-sekwencjonowaniu w celuB; while(A){}
. Cytując maszynę abstrakcyjną C11, podkreśl moją: „Obecność punktu sekwencji między oceną wyrażeń A i B oznacza, że każde obliczenie wartości i efekt uboczny związany z A jest sekwencjonowane przed każdym obliczeniem wartości i efektem ubocznym związanym z B ”. WartośćA
jest wyraźnie używana (przez pętlę).Byłem przekonany, że to zwykły stary błąd. Moje testy zostawiam poniżej, w szczególności odniesienie do dyskusji w komisji standardowej z pewnych powodów, które wcześniej miałem.
Myślę, że jest to niezdefiniowane zachowanie (patrz koniec), a Clang ma tylko jedną implementację. GCC rzeczywiście działa zgodnie z oczekiwaniami, optymalizując tylko instrukcję
unreachable
print, ale pozostawiając pętlę. Trochę dziwnego sposobu, w jaki Clang dziwnie podejmuje decyzje, łącząc wstawianie i określając, co może zrobić z pętlą.Zachowanie jest wyjątkowo dziwne - usuwa końcowy nadruk, więc „widzi” nieskończoną pętlę, ale także pozbywa się jej.
O ile mogę powiedzieć, jest jeszcze gorzej. Usuwając wstawkę otrzymujemy:
więc funkcja została utworzona, a połączenie zoptymalizowane. Jest to nawet bardziej odporne niż oczekiwano:
skutkuje bardzo nieoptymalnym zestawem funkcji, ale wywołanie funkcji jest ponownie zoptymalizowane! Nawet gorzej:
Zrobiłem kilka innych testów, dodając zmienną lokalną i zwiększając ją, przekazując wskaźnik, używając
goto
etc ... W tym momencie zrezygnowałem. Jeśli musisz użyć clangwykonuje pracę. Jest do bani w optymalizacji (oczywiście) i pozostawia zbędny finał
printf
. Przynajmniej program się nie zatrzymuje. Może w końcu GCC?Uzupełnienie
Po dyskusji z Davidem stwierdzam, że standard nie mówi „jeśli warunek jest stały, możesz nie założyć, że pętla się kończy”. Jako taki, i zgodnie ze standardem nie ma obserwowalnego zachowania (jak zdefiniowano w standardzie), argumentowałbym tylko za spójnością - jeśli kompilator optymalizuje pętlę, ponieważ zakłada, że się kończy, nie powinien optymalizować następujących instrukcji.
Heck n1528 ma to jako niezdefiniowane zachowanie, jeśli dobrze to przeczytam. konkretnie
Odtąd myślę, że może ona przerodzić się w dyskusję o tym, czego chcemy (oczekiwaliśmy?), A nie o to, co jest dozwolone.
źródło
Wygląda na to, że jest to błąd w kompilatorze Clanga. Jeśli nie ma żadnego przymusu, aby
die()
funkcja była funkcją statyczną, usuń jąstatic
i zrób toinline
:Działa zgodnie z oczekiwaniami po kompilacji z kompilatorem Clang, a także jest przenośny.
Eksplorator kompilatorów (godbolt.org) - klang 9.0.0
-O3 -std=c11 -pedantic-errors
źródło
static inline
?Wydaje się, że dla mnie działają:
w godbolt
Jawne mówienie Clangowi, aby nie optymalizował, że jedna funkcja powoduje, że nieskończona pętla jest emitowana zgodnie z oczekiwaniami. Mamy nadzieję, że istnieje sposób, aby selektywnie wyłączyć poszczególne optymalizacje zamiast po prostu je wszystkie wyłączyć. Jednak Clang nadal odmawia wydania kodu dla drugiego
printf
. Aby to zmusić, musiałem dodatkowo zmodyfikować kod wewnątrz,main
aby:Wygląda na to, że musisz wyłączyć optymalizacje dla funkcji nieskończonej pętli, a następnie upewnić się, że twoja nieskończona pętla jest wywoływana warunkowo. W prawdziwym świecie i tak jest to prawie zawsze.
źródło
printf
generowanie drugiego, jeśli pętla rzeczywiście trwa wiecznie, ponieważ w takim przypadku drugiprintf
naprawdę jest nieosiągalny i dlatego można go usunąć. (Błąd Clanga polega zarówno na wykryciu nieosiągalności, jak i na usunięciu pętli, aby osiągnąć nieosiągalny kod).__attribute__ ((optimize(1)))
, ale clang ignoruje je jako nieobsługiwane: godbolt.org/z/4ba2HM . gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.htmlImplementacja zgodna może, i wiele praktycznych, może narzucić dowolne limity czasu trwania programu lub liczby instrukcji, które wykona, i zachowywać się w sposób arbitralny, jeśli limity te zostaną naruszone lub - zgodnie z zasadą „jak gdyby” - jeśli stwierdzi, że zostaną one nieuchronnie naruszone. Pod warunkiem, że wdrożenie może z powodzeniem przetworzyć co najmniej jeden program, który nominalnie wykonuje wszystkie limity wymienione w N1570 5.2.4.1, nie przekraczając żadnych limitów translacji, istnienia limitów, zakresu ich udokumentowania i skutków ich przekroczenia są: wszystkie kwestie dotyczące jakości wdrożenia poza jurysdykcją normy.
Myślę, że intencja Standardu jest całkiem jasna, że kompilatory nie powinny zakładać, że
while(1) {}
pętla bez efektów ubocznych ibreak
instrukcji nie zostanie zakończona. Wbrew temu, co niektórzy mogą sądzić, autorzy Standardu nie zapraszali pisarzy kompilatorów, aby byli głupi lub tępi. Implementacja zgodna może być przydatna do podjęcia decyzji o zakończeniu dowolnego programu, który, jeśli nie zostanie przerwany, wykona więcej instrukcji wolnych od skutków ubocznych niż atomów we wszechświecie, ale implementacja wysokiej jakości nie powinna wykonywać takich działań na podstawie jakichkolwiek założeń dotyczących zakończenie, ale raczej na tej podstawie, że może to być przydatne i nie byłoby (w przeciwieństwie do zachowania klanu) gorsze niż bezużyteczne.źródło
Pętla nie ma skutków ubocznych, dlatego można ją zoptymalizować. Pętla jest w rzeczywistości nieskończoną liczbą iteracji zerowych jednostek pracy. Jest to niezdefiniowane w matematyce i logice, a standard nie mówi, czy implementacja jest dozwolona do wykonania nieskończonej liczby rzeczy, jeśli każda rzecz może być wykonana w czasie zerowym. Interpretacja Clanga jest całkowicie rozsądna w traktowaniu nieskończoności razy zero jako nieskończoności. Standard nie mówi, czy nieskończona pętla może się zakończyć, jeśli cała praca w pętli zostanie faktycznie zakończona.
Kompilator może optymalizować wszystko, co nie jest możliwe do zaobserwowania, zgodnie z definicją zawartą w standardzie. Obejmuje to czas wykonania. Nie jest konieczne zachowanie faktu, że pętla, jeśli nie zostanie zoptymalizowana, zajmie nieskończoną ilość czasu. Dozwolone jest zmienianie tego na znacznie krótszy czas działania - w rzeczywistości jest to punkt większości optymalizacji. Twoja pętla została zoptymalizowana.
Nawet jeśli clang przetłumaczył kod naiwnie, można sobie wyobrazić optymalizujący procesor, który może ukończyć każdą iterację w połowie czasu poprzedniej iteracji. To dosłownie uzupełniłoby nieskończoną pętlę w skończonym czasie. Czy taki optymalizujący procesor narusza standard? Absurdalne wydaje się stwierdzenie, że optymalizujący procesor naruszyłby standard, jeśli jest zbyt dobry w optymalizacji. To samo dotyczy kompilatora.
źródło
Przepraszam, jeśli tak absurdalnie nie jest, natknąłem się na ten post i wiem, ponieważ moje lata z dystrybucją Gentoo Linux, że jeśli chcesz, aby kompilator nie zoptymalizował twojego kodu, powinieneś użyć opcji -O0 (zero). Byłem ciekawy tego, skompilowałem i uruchomiłem powyższy kod, a pętla robi się bez końca. Kompilacja przy użyciu clang-9:
źródło
Pusta
while
pętla nie ma żadnych skutków ubocznych w systemie.Dlatego Clang go usuwa. Istnieją „lepsze” sposoby osiągnięcia zamierzonego zachowania, które zmuszają cię do bardziej oczywistych zamiarów.
while(1);
jest baaadd.źródło
abort()
lubexit()
. Jeśli pojawi się sytuacja, w której funkcja ustali, że (być może w wyniku uszkodzenia pamięci) dalsze wykonywanie byłoby gorsze niż niebezpieczne, częstym domyślnym zachowaniem bibliotek osadzonych jest wywoływanie funkcji, która wykonuje funkcjęwhile(1);
. Może to być przydatne dla kompilatora mieć opcje , aby zastąpić bardziej przydatnych zachowań, ale każdy pisarz kompilator, którzy nie mogą dowiedzieć się, jak traktować taką prostą konstrukcję jako bariera dla dalszego wykonywania programu jest niekompetentny można ufać złożonych optymalizacje.