Najlepsza odpowiedź to błędne (ale powszechne) nieporozumienie:
Niezdefiniowane zachowanie jest właściwością czasu wykonywania *. To NIE MOŻE „podróży w czasie”!
Niektóre operacje są zdefiniowane (standardowo) jako mające skutki uboczne i nie można ich zoptymalizować. Operacje, które wykonują operacje we / wy lub uzyskują dostęp do volatile
zmiennych, należą do tej kategorii.
Istnieje jednak zastrzeżenie: UB może być dowolnym zachowaniem, w tym zachowaniem, które cofa poprzednie operacje. W niektórych przypadkach może to mieć podobne konsekwencje do optymalizacji wcześniejszego kodu.
W rzeczywistości jest to zgodne z cytatem w górnej odpowiedzi (moje wyróżnienie):
Zgodna implementacja wykonująca dobrze uformowany program będzie dawać takie samo obserwowalne zachowanie, jak jedno z możliwych wykonań odpowiedniej instancji maszyny abstrakcyjnej z tym samym programem i tymi samymi danymi wejściowymi.
Jeśli jednak takie wykonanie zawiera nieokreśloną operację, niniejsza Norma Międzynarodowa nie nakłada żadnych wymagań na implementację wykonującą ten program z tym wejściem (nawet w odniesieniu do operacji poprzedzających pierwszą niezdefiniowaną operację).
Tak, ten cytat nie powiedzieć „nie, nawet w odniesieniu do operacji poprzedzających pierwszy niezdefiniowanej operacji” , ale zauważ, że to jest konkretnie o kod, który jest wykonany , nie tylko skompilowany.
W końcu niezdefiniowane zachowanie, które w rzeczywistości nie jest osiągnięte, nic nie robi, a aby wiersz zawierający UB został faktycznie osiągnięty, kod, który go poprzedza, musi zostać wykonany jako pierwszy!
Więc tak, po wykonaniu UB wszelkie efekty poprzednich operacji stają się niezdefiniowane. Ale dopóki to się nie stanie, wykonanie programu jest dobrze zdefiniowane.
Należy jednak pamiętać, że wszystkie uruchomienia programu, które powodują takie zdarzenie, można zoptymalizować pod kątem równoważnych programów, w tym programów wykonujących poprzednie operacje, ale następnie cofających ich efekty. W konsekwencji poprzedni kod może zostać zoptymalizowany, jeśli byłoby to równoważne z cofnięciem ich skutków ; w przeciwnym razie nie może. Poniżej przykład.
* Uwaga: nie jest to niespójne z UB występującym w czasie kompilacji . Jeśli kompilator rzeczywiście może udowodnić, że kod UB będzie zawsze wykonywany dla wszystkich danych wejściowych, wówczas UB może wydłużyć czas kompilacji. Wymaga to jednak wiedzy, że cały poprzedni kod w końcu powróci , co jest silnym wymogiem. Ponownie, zobacz poniżej przykład / wyjaśnienie.
Aby było to konkretne, zwróć uwagę, że poniższy kod musi wydrukować foo
i czekać na dane wejściowe, niezależnie od jakiegokolwiek niezdefiniowanego zachowania, które po nim następuje:
printf("foo")
getchar()
*(char*)1 = 1
Należy jednak pamiętać, że nie ma gwarancji, że foo
pozostanie na ekranie po wystąpieniu UB, ani że wpisany znak nie będzie już w buforze wejściowym; obie te operacje można „cofnąć”, co ma podobny efekt do „podróży w czasie” UB.
Gdyby getchar()
linii nie było, byłoby to dozwolone , gdyby linie były zoptymalizowane, wtedy i tylko wtedy , gdy byłoby to nie do odróżnienia od wyprowadzania, foo
a następnie „usuwania”.
To, czy te dwa elementy byłyby nierozróżnialne, zależałoby całkowicie od implementacji (tj. Od kompilatora i biblioteki standardowej). Na przykład, czy możesz printf
zablokować tutaj swój wątek, czekając na inny program, aby odczytać dane wyjściowe? A może natychmiast wróci?
Jeśli może się tutaj zablokować, wówczas inny program może odmówić odczytania pełnego wyjścia i może nigdy nie powrócić, aw konsekwencji UB może nigdy nie wystąpić.
Jeśli może natychmiast powrócić tutaj, to wiemy, że musi powrócić, a zatem optymalizacja jest całkowicie nie do odróżnienia od wykonywania, a następnie cofania efektów.
Oczywiście, ponieważ kompilator wie, jakie zachowanie jest dopuszczalne dla jego konkretnej wersji printf
, może odpowiednio zoptymalizować, a co za tym idzie printf
, w niektórych przypadkach może zostać zoptymalizowany, aw innych nie. Ale, znowu, uzasadnienie jest takie, że byłoby to nie do odróżnienia od niewykonywania poprzednich operacji przez UB, a nie, że poprzedni kod jest „zatruty” z powodu UB.
a
nie jest używany (z wyjątkiem samego obliczenia) i po prostu usunąća