Co dokładnie oznacza „IB” i „UB”?

110

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?

cHao
źródło
5
Jeśli zdecydujesz się wycofać zmiany wprowadzone przez kogoś innego, upewnij się, że pisownia, interpunkcja i gramatyka są doskonałe. Wycofywanie zmian, które są znacznym ulepszeniem w stosunku do oryginalnego tekstu, jest bezcelowe.
Robert Harvey

Odpowiedzi:

139

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.

Tomasz
źródło
11
Nadal czekam, aż demon wyleci komuś z nosa z powodu niezdefiniowanego zachowania w C ++. Myślę, że stanie się to, gdy pierwsze kompilatory będą w pełni zgodne z nowym standardem C ++.
OregonGhost
4
@OregonGhost: Myślę, że masz rację. Widziałem to kilka razy z jednorożcami, ale nigdy z demonami.
Thomas
33
@OregonGhost - standard nie określa, ile rogów ma mieć demon.
DVK
5
@Michael Burr: Wolę „zapalić się”. Jest to ewidentnie katastrofalne i ma co najmniej niejasną atmosferę wiarygodności (sprzęt komputerowy czasami się zapala, co prawda z powodu sprzętu, a nie awarii oprogramowania w przypadku dowolnego systemu, w którym czytasz ten wątek).
Steve Jessop
1
To zabawne, że nikt, kto odpowiedział na to pytanie, nie ma mniej reputacji niż 30 tys.
19

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.

jalf
źródło
1
UB należy unikać, jeśli zależy Ci na przenośności . Konkretna implementacja może definiować, co dzieje się w przypadku określonego, niezdefiniowanego zachowania, aw niektórych przypadkach (zwłaszcza sterowników urządzeń i mniejszych systemów wbudowanych) musisz użyć tych rzeczy.
Jerry Coffin
3
@Jerry: Nie, należy unikać UB, jeśli jest całkowicie nieokreślone . Jeśli platforma / implementacja / środowisko uruchomieniowe / kompilator daje dalsze gwarancje, możesz polegać na zachowaniu i stracić przenośność. Ale wtedy nie jest już tak nieokreślony ... Jednak przez większość czasu nie masz takich gwarancji, a wartość undefined jest po prostu niezdefiniowana i należy jej unikać za wszelką cenę.
jalf
„spójny” może być mylącym opisem nieokreślonego zachowania. Musi być spójny z ogólnym kontekstem operacji, na przykład jeśli wyrażenie ma „nieokreśloną wartość”, to wynikiem musi być wartość, jeśli ją przechowujesz, to przechowywana wartość musi być następnie porównywana ze sobą i tak dalej. Ale nieokreślone wyniki nie muszą być spójne w czasie (te same dane wyjściowe dla tych samych danych wejściowych, jeśli uruchomisz je ponownie), ani nawet deterministyczne.
Steve Jessop
„już nie tak nieokreślony” - jest dokładnie taki sam, jak nieokreślony w standardzie , a UB to skrótowe znaczenie niezdefiniowane przez standard. W twoim przykładzie jest to zdefiniowane przez implementację. W tej kwestii możesz polegać na zachowaniu, które nie jest zdefiniowane przez standard lub implementację, jeśli sprawdziłeś kod obiektowy i nie planujesz ponownej kompilacji nigdy więcej ;-)
Steve Jessop
„musi następnie porównać sobie równe”. Hmm, chyba że to NaN. W każdym razie musi mieć takie zachowanie, jakiego wymaga jego typ.
Steve Jessop
8
  • 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).

Michael Burr
źródło
4

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.

Kerrek SB
źródło
Jeśli norma wskazuje, że akcja wywołuje zachowanie zdefiniowane w implementacji, implementacje muszą określić spójne zachowanie wynikające z tej akcji. Niestety, nie ma kategorii zachowań, w przypadku których implementacja wymagałaby określenia możliwych konsekwencji, ale nie wymagałaby, aby jakikolwiek konkretny skutek następował konsekwentnie.
supercat