Kilka razy widziałem terminy „IB” i „UB”, szczególnie w kontekście C ++. Próbowałem je wyszukać w Google, ale najwyraźniej te dwuliterowe kombinacje mają wiele zastosowań. : P
Więc pytam cię ... co mają na myśli, kiedy mówi się, że są złe?
Kilka razy widziałem terminy „IB” i „UB”, szczególnie w kontekście C ++. Próbowałem je wyszukać w Google, ale najwyraźniej te dwuliterowe kombinacje mają wiele zastosowań. : P
Więc pytam cię ... co mają na myśli, kiedy mówi się, że są złe?
Odpowiedzi:
IB: Zachowanie zdefiniowane w implementacji. Standard pozostawia określenie konkretnego kompilatora / platformy w gestii konkretnego kompilatora / platformy, ale wymaga, aby zostało ono zdefiniowane.
Używanie zachowania zdefiniowanego w implementacji może być przydatne, ale sprawia, że kod jest mniej przenośny.
UB: niezdefiniowane zachowanie. Standard nie określa, jak powinien zachowywać się program wywołujący niezdefiniowane zachowanie. Znany również jako „demony nosowe”, ponieważ teoretycznie może sprawiać, że demony wylatują z nosa.
Używanie niezdefiniowanego zachowania jest prawie zawsze złym pomysłem. Nawet jeśli czasami wydaje się działać, każda zmiana środowiska, kompilatora lub platformy może losowo uszkodzić Twój kod.
Zachowanie zdefiniowane w ramach implementacji i niezdefiniowane zachowanie
Standard C ++ jest bardzo szczegółowy, jeśli chodzi o skutki różnych konstrukcji, w szczególności należy zawsze pamiętać o następujących kategoriach problemów :
Niezdefiniowane zachowanie oznacza, że nie ma absolutnie żadnych gwarancji. Kod może zadziałać, może podpalić twój dysk twardy lub sprawić, że demony wylecą ci z nosa . Jeśli chodzi o język C ++, może się zdarzyć absolutnie wszystko. W praktyce oznacza to na ogół, że masz nieodwracalny błąd. Jeśli tak się stanie, nie możesz naprawdę ufać niczemu w swojej aplikacji (ponieważ jednym z efektów tego niezdefiniowanego zachowania mogło być po prostu zepsucie pamięci używanej przez resztę aplikacji). Nie jest wymagana spójność, więc dwukrotne uruchomienie programu może dać różne wyniki. Może to zależeć od faz księżyca, koloru koszuli, którą nosisz, lub absolutnie wszystkiego innego.
Nieokreślone zachowanie oznacza, że program musi wykonać coś rozsądnego i konsekwentnego, ale nie jest to wymagane do udokumentowania tego.
Zachowanie zdefiniowane przez implementację jest podobne do nieokreślonego, ale musi być również udokumentowane przez autorów kompilatora. Przykładem tego jest wynik a reinterpret_cast
. zwykle po prostu zmienia typ wskaźnika bez modyfikowania adresu, ale mapowanie jest w rzeczywistości zdefiniowane przez implementację, więc kompilator może mapować na zupełnie inny adres, o ile udokumentował ten wybór. Innym przykładem jest rozmiar int. Standard C ++ nie dba o to, czy ma 2, 4 czy 8 bajtów, ale musi to zostać udokumentowane przez kompilator
Ale wspólne dla nich wszystkich jest to, że najlepiej ich unikać. Jeśli to możliwe, trzymaj się zachowania, które jest w 100% określone przez sam standard C ++. W ten sposób masz gwarancję przenośności.
Często musisz również polegać na pewnych zachowaniach zdefiniowanych w implementacji. Może to być nieuniknione, ale nadal powinieneś zwracać na to uwagę i mieć świadomość, że polegasz na czymś, co może się zmieniać między różnymi kompilatorami.
Z drugiej strony zawsze należy unikać niezdefiniowanych zachowań . Ogólnie rzecz biorąc, powinieneś po prostu założyć, że powoduje to eksplozję programu w taki czy inny sposób.
IB: to zachowanie zdefiniowane w ramach implementacji - kompilator musi udokumentować, co robi. >>
Przykładem jest wykonanie operacji na wartości ujemnej.
UB: niezdefiniowane zachowanie - kompilator może zrobić wszystko, w tym po prostu zawiesić się lub dać nieprzewidywalne wyniki. Dereferencjonowanie pustego wskaźnika należy do tej kategorii, ale także subtelniejsze rzeczy, takie jak arytmetyka wskaźnika, która wykracza poza granice obiektu tablicy.
Innym pokrewnym terminem jest „nieokreślone zachowanie”. Jest to rodzaj między zachowaniami zdefiniowanymi w implementacji i niezdefiniowanymi. dla nieokreślonego zachowania kompilator musi zrobić coś zgodnie ze standardem, ale dokładnie to, jakie wybory daje mu standard, zależy od kompilatora i nie musi być definiowane (ani nawet spójne). Do tej kategorii należą takie rzeczy, jak kolejność oceny wyrażeń podrzędnych. Kompilator może wykonywać te czynności w dowolnej kolejności i może to robić inaczej w różnych kompilacjach lub nawet w różnych uruchomieniach tej samej kompilacji (mało prawdopodobne, ale dozwolone).
Krótka wersja:
Zachowanie zdefiniowane w ramach implementacji (IB): prawidłowo zaprogramowane, ale nieokreślone *
Niezdefiniowane zachowanie (UB): Niepoprawnie zaprogramowane (tj. Błąd !)
*) „nieokreślony” jeśli chodzi o standard językowy, będzie on oczywiście określony na dowolnej stałej platformie.