Nieokreślone, nieokreślone i zdefiniowane w implementacji zachowanie

529

Co to jest niezdefiniowane zachowanie w C i C ++? Co z nieokreślonym zachowaniem i zachowaniem zdefiniowanym w implementacji? Jaka jest różnica między nimi?

Zolomon
źródło
1
Byłem całkiem pewien, że to zrobiliśmy, ale nie mogę tego znaleźć. Zobacz także: stackoverflow.com/questions/2301372/...
dmckee --- były moderator kociąt
1
Z pliku
jamesdlin,
1
Oto interesująca dyskusja (sekcja „Załącznik L i niezdefiniowane zachowanie”).
Owen,

Odpowiedzi:

404

Niezdefiniowane zachowanie jest jednym z tych aspektów języka C i C ++, które mogą być zaskakujące dla programistów pochodzących z innych języków (inne języki starają się to lepiej ukrywać). Zasadniczo możliwe jest pisanie programów C ++, które nie zachowują się w przewidywalny sposób, mimo że wiele kompilatorów C ++ nie zgłasza żadnych błędów w programie!

Spójrzmy na klasyczny przykład:

#include <iostream>

int main()
{
    char* p = "hello!\n";   // yes I know, deprecated conversion
    p[0] = 'y';
    p[5] = 'w';
    std::cout << p;
}

Zmienna pwskazuje na literał łańcucha "hello!\n", a dwa poniższe przypisania próbują zmodyfikować ten literał łańcucha. Co robi ten program? Zgodnie z sekcją 2.14.5 pkt 11 standardu C ++ wywołuje niezdefiniowane zachowanie :

Efekt próby modyfikacji literału łańcuchowego jest niezdefiniowany.

Słyszę krzyki ludzi: „Ale poczekaj, mogę to skompilować bez problemu i uzyskać wynik yellow” lub „Co to znaczy niezdefiniowane, literały łańcuchowe są przechowywane w pamięci tylko do odczytu, więc pierwsza próba przypisania powoduje zrzut pamięci”. To jest dokładnie problem z niezdefiniowanym zachowaniem. Zasadniczo standard pozwala na wszystko, co się stanie, gdy wywołasz niezdefiniowane zachowanie (nawet demony nosowe). Jeśli zachowuje się „prawidłowe” zachowanie zgodnie z twoim mentalnym modelem języka, ten model jest po prostu zły; Standard C ++ ma jedyny głos, kropkę.

Inne przykłady nieokreślonego zachowania obejmują dostęp do tablicy poza jej granice, dereferencję wskaźnika zerowego , dostęp do obiektów po zakończeniu ich życia lub pisanie rzekomo sprytnych wyrażeń, takich jak i++ + ++i.

W sekcji 1.9 standardu C ++ wspomniano również o dwóch mniej niebezpiecznych zachowaniach braci, zachowaniu nieokreślonym i zachowaniu zdefiniowanym w ramach implementacji :

Opisy semantyczne w niniejszej Normie Międzynarodowej definiują sparametryzowaną niedeterministyczną maszynę abstrakcyjną.

Niektóre aspekty i operacje maszyny abstrakcyjnej są opisane w niniejszej Normie Międzynarodowej jako zdefiniowane w implementacji (na przykład sizeof(int)). Stanowią one parametry abstrakcyjnej maszyny. Każde wdrożenie powinno zawierać dokumentację opisującą jego cechy i zachowanie w tym zakresie.

Niektóre inne aspekty i operacje maszyny abstrakcyjnej są opisane w niniejszej Normie Międzynarodowej jako nieokreślone (na przykład kolejność oceny argumentów funkcji). Tam, gdzie to możliwe, niniejszy standard międzynarodowy określa zestaw dopuszczalnych zachowań. Definiują one niedeterministyczne aspekty abstrakcyjnej maszyny.

Niektóre inne operacje są opisane w niniejszej Normie Międzynarodowej jako niezdefiniowane (na przykład efekt dereferencji wskaźnika zerowego). [ Uwaga : ta Norma Międzynarodowa nie nakłada żadnych wymagań na zachowanie programów, które zawierają niezdefiniowane zachowanie. - uwaga końcowa ]

W szczególności sekcja 1.3.24 stanowi:

Dopuszczalne niezdefiniowane zachowanie obejmuje od całkowitego zignorowania sytuacji z nieprzewidzianymi rezultatami , zachowania podczas tłumaczenia lub wykonywania programu w udokumentowany sposób charakterystyczny dla środowiska (z wydaniem komunikatu diagnostycznego lub bez niego), aż do zakończenia tłumaczenia lub wykonania (z wydaniem komunikatu diagnostycznego).

Co możesz zrobić, aby uniknąć nieokreślonego zachowania? Zasadniczo musisz czytać dobre książki C ++ autorów, którzy wiedzą o czym mówią. Pieprzyć samouczki internetowe. Pieprzyć bullschildt.

fredoverflow
źródło
6
To dziwny fakt, który wynika ze scalenia, że ​​ta odpowiedź obejmuje tylko C ++, ale tagi tego pytania obejmują C. C ma inne pojęcie „niezdefiniowanego zachowania”: Nadal będzie wymagało implementacji, aby podawać komunikaty diagnostyczne, nawet jeśli zachowanie jest również określone jako być niezdefiniowanym dla niektórych naruszeń reguł (naruszenia ograniczeń).
Johannes Schaub - litb
8
@Benoit Jest to zachowanie nieokreślone, ponieważ standard mówi, że jest to zachowanie nieokreślone, kropka. W niektórych systemach rzeczywiście literały łańcuchowe są przechowywane w segmencie tekstowym tylko do odczytu, a program ulegnie awarii, jeśli spróbujesz zmodyfikować literały łańcuchowe. W innych systemach dosłowny ciąg znaków rzeczywiście będzie się zmieniał. Norma nie określa, co ma się stać. To właśnie oznacza niezdefiniowane zachowanie.
fredoverflow
5
@FredOverflow, Dlaczego dobry kompilator pozwala nam kompilować kod, który daje niezdefiniowane zachowanie? Dokładnie to, co dobre, może sporządzania tego rodzaju kodu dać? Dlaczego wszystkie dobre kompilatory nie dały nam wielkiego czerwonego znaku ostrzegawczego, gdy próbujemy skompilować kod, który daje niezdefiniowane zachowanie?
Pacerier
14
@Pacerier Istnieją pewne rzeczy, których nie można sprawdzić w czasie kompilacji. Na przykład nie zawsze jest możliwe zagwarantowanie, że wskaźnik zerowy nigdy nie zostanie usunięty z dereferencji, ale jest to niezdefiniowane.
Tim Seguine,
4
@Celeritas, niezdefiniowane zachowanie może być niedeterministyczne. Na przykład nie można z góry wiedzieć, jaka będzie zawartość niezainicjowanej pamięci, np. int f(){int a; return a;}: wartość amoże zmieniać się między wywołaniami funkcji.
Mark
96

Cóż, jest to w zasadzie prosta kopia-wklej ze standardu

3.4.1 1 zachowanie zdefiniowane w ramach wdrożenia nieokreślone zachowanie, w którym każde wdrożenie dokumentuje sposób dokonania wyboru

2 PRZYKŁAD Przykładem zachowania zdefiniowanego w implementacji jest propagacja bitu wyższego rzędu, gdy liczba całkowita ze znakiem jest przesunięta w prawo.

3.4.3 1 zachowanie nieokreślone , po zastosowaniu nieprzenośnej lub błędnej konstrukcji programu lub błędnych danych, dla których niniejsza Norma Międzynarodowa nie nakłada żadnych wymagań

2 UWAGA Możliwe niezdefiniowane zachowanie się obejmuje od całkowitego zignorowania sytuacji z nieprzewidzianymi wynikami, do zachowania podczas tłumaczenia lub wykonywania programu w udokumentowany sposób charakterystyczny dla środowiska (z wydaniem komunikatu diagnostycznego lub bez niego), do zakończenia tłumaczenia lub wykonania (z wydanie komunikatu diagnostycznego).

3 PRZYKŁAD Przykładem niezdefiniowanego zachowania jest zachowanie przy przepełnieniu liczb całkowitych.

3.4.4 1 nieokreślone zachowanie użycie nieokreślonej wartości lub inne zachowanie, w przypadku gdy niniejsza norma międzynarodowa zapewnia dwie lub więcej możliwości i nie nakłada żadnych dalszych wymagań, w odniesieniu do których wybrano się w każdym przypadku

2 PRZYKŁAD Przykładem nieokreślonego zachowania jest kolejność, w jakiej są oceniane argumenty funkcji.

Mrówka
źródło
3
Jaka jest różnica między zachowaniem zdefiniowanym w implementacji a nieokreślonym?
Zolomon,
26
@Zolomon: Tak jak to mówi: zasadniczo to samo, tyle że w przypadku implementacji zdefiniowanej implementacja wymaga udokumentowania (w celu zagwarantowania), co dokładnie się wydarzy, podczas gdy w przypadku nieokreślonego implementacja nie jest wymagana do udokumentowania lub zagwarantować cokolwiek.
AnT
1
@Zolomon: Odzwierciedla to różnica między 3.4.1 a 2.4.4.
sbi
8
@Celeritas: Hipernowoczesne kompilatory mogą działać lepiej. Biorąc pod uwagę, int foo(int x) { if (x >= 0) launch_missiles(); return x << 1; }że kompilator może ustalić, że ponieważ wszystkie sposoby wywoływania funkcji, która nie uruchamia pocisków, wywołują Nieokreślone zachowanie, mogą wywołać launch_missiles()bezwarunkowe wywołanie .
supercat
2
@northerner Jak stwierdza cytat, nieokreślone zachowanie jest zwykle ograniczone do ograniczonego zestawu możliwych zachowań. W niektórych przypadkach możesz nawet dojść do wniosku, że wszystkie te możliwości są dopuszczalne w danym kontekście, w którym to przypadku nieokreślone zachowanie nie stanowi żadnego problemu. Niezdefiniowane zachowanie jest całkowicie nieograniczone (np. „Program może zdecydować o sformatowaniu dysku twardego”). Niezdefiniowane zachowanie zawsze stanowi problem.
AnT
60

Może łatwiejsze do zrozumienia sformułowanie byłoby łatwiejsze niż rygorystyczna definicja norm.

zachowanie zdefiniowane w implementacji
Język mówi, że mamy typy danych. Dostawcy kompilatorów określają, jakich rozmiarów będą używać, i dostarczają dokumentację tego, co zrobili.

niezdefiniowane zachowanie
Robisz coś złego. Na przykład masz bardzo dużą wartość int, która nie pasuje char. Jak obliczyć tę wartość char? właściwie nie ma mowy! Wszystko może się zdarzyć, ale najrozsądniejszą rzeczą byłoby wziąć pierwszy bajt tego inta i wstawić go char. Po prostu źle jest to zrobić, aby przypisać pierwszy bajt, ale tak dzieje się pod maską.

nieokreślone zachowanie
Która z tych funkcji jest wykonywana jako pierwsza?

void fun(int n, int m);

int fun1()
{
  cout << "fun1";
  return 1;
}
int fun2()
{
  cout << "fun2";
  return 2;
}
...
fun(fun1(), fun2()); // which one is executed first?

Język nie określa oceny, od lewej do prawej lub od prawej do lewej! Tak więc nieokreślone zachowanie może, ale nie musi, skutkować nieokreślonym zachowaniem, ale z pewnością twój program nie powinien generować nieokreślonego zachowania.


@eSKay Myślę, że twoje pytanie jest warte edycji odpowiedzi, aby wyjaśnić więcej :)

bo fun(fun1(), fun2());czy zachowanie nie jest „zdefiniowane”? W końcu kompilator musi wybrać jeden lub drugi kurs?

Różnica między definicją implementacji a nieokreśloną polega na tym, że kompilator powinien wybrać zachowanie w pierwszym przypadku, ale nie musi tak być w drugim przypadku. Na przykład implementacja musi mieć jedną i tylko jedną definicję sizeof(int). Nie można więc powiedzieć, że sizeof(int)dla jednej części programu jest to 4, a dla innych 8. W przeciwieństwie do nieokreślonego zachowania, w którym kompilator może powiedzieć OK, będę oceniał te argumenty od lewej do prawej, a argumenty następnej funkcji są oceniane od prawej do lewej. Może się to zdarzyć w tym samym programie, dlatego nazywa się to nieokreślonym . W rzeczywistości C ++ można by uprościć, gdyby określono niektóre nieokreślone zachowania. Spójrz tutaj na odpowiedź dr Stroustrupa na to :

Twierdzi się, że różnica między tym, co można wytworzyć, dając kompilatorowi swobodę i wymagając „zwykłej oceny od lewej do prawej” może być znacząca. Nie jestem przekonany, ale z niezliczonymi kompilatorami „tam” korzystającymi z wolności i niektórymi ludźmi z pasją broniącymi tej wolności, zmiana byłaby trudna i może zająć dziesięciolecia, aby dotrzeć do odległych zakątków światów C i C ++. Jestem rozczarowany, że nie wszystkie kompilatory ostrzegają przed kodem takim jak ++ i + i ++. Podobnie, kolejność oceny argumentów jest nieokreślona.

IMO zdecydowanie zbyt wiele „rzeczy” pozostaje niezdefiniowanych, nieokreślonych, zdefiniowanych w implementacji itp. Łatwo jednak powiedzieć, a nawet podać przykłady, ale trudne do naprawienia. Należy również zauważyć, że nie jest wcale tak trudno uniknąć większości problemów i stworzyć przenośny kod.

ARAK
źródło
1
bo czy fun(fun1(), fun2());to nie zachowanie "implementation defined"? W końcu kompilator musi wybrać jeden lub drugi kurs?
Lazer
1
@AraK: dzięki za wyjaśnienie. Teraz to rozumiem. Przy okazji, "I am gonna evaluate these arguments left-to-right and the next function's arguments are evaluated right-to-left"rozumiem , że tak się canstało. Czy tak naprawdę jest w kompilatorach, których używamy obecnie?
Lazer,
1
@eSKay Musisz zapytać guru o to, kto pobrudził sobie wiele kompilatorów :) AFAIK VC zawsze ocenia argumenty od prawej do lewej.
AraK
4
@Lazer: Może się zdarzyć. Prosty scenariusz: foo (bar, boz ()) i foo (boz (), bar), gdzie bar jest liczbą całkowitą, a boz () jest funkcją zwracającą liczbę całkowitą. Załóżmy, że procesor ma przekazać parametry w rejestrach R0-R1. Wyniki funkcji są zwracane w R0; funkcje mogą zniszczyć R1. Ocena „bar” przed „boz ()” wymagałaby zapisania kopii paska gdzie indziej przed wywołaniem boz (), a następnie załadowania tej zapisanej kopii. Ocena „bar” po „boz ()” pozwoli uniknąć przechowywania pamięci i ponownego pobierania i jest optymalizacją, którą wiele kompilatorów zrobiłoby bez względu na ich kolejność na liście argumentów.
supercat
6
Nie wiem o C ++, ale standard C mówi, że konwersja int na char jest albo implementacją zdefiniowaną, albo nawet dobrze zdefiniowaną (w zależności od rzeczywistych wartości i podpisów typów). Patrz C99 §6.3.1.3 (niezmieniony w C11).
Nikolai Ruhe
27

Z oficjalnego dokumentu uzasadnienia C.

Terminy nieokreślone zachowanie, niezdefiniowane zachowanie i zachowanie zdefiniowane w implementacji są używane do kategoryzacji wyników pisania programów, których właściwości Standard nie opisuje lub nie może całkowicie opisać. Celem przyjęcia tej kategoryzacji jest dopuszczenie pewnej różnorodności wśród implementacji, która pozwala, aby jakość implementacji była aktywną siłą na rynku, a także dopuszczenie niektórych popularnych rozszerzeń, bez usuwania cła zgodności ze standardem. Dodatek F do katalogu Standardowego zawiera zachowania, które należą do jednej z tych trzech kategorii.

Nieokreślone zachowanie daje implementatorowi pewną swobodę w tłumaczeniu programów. Ta szerokość geograficzna nie rozciąga się na tyle, że nie udało się przetłumaczyć programu.

Niezdefiniowane zachowanie pozwala licencji na implementację nie wychwytywać niektórych błędów programu, które są trudne do zdiagnozowania. Identyfikuje również obszary możliwego rozszerzenia języka zgodnego: implementator może ulepszyć język, podając definicję oficjalnie niezdefiniowanego zachowania.

Zachowanie zdefiniowane w implementacji daje implementatorowi swobodę wyboru odpowiedniego podejścia, ale wymaga wyjaśnienia użytkownikowi tego wyboru. Zachowania oznaczone jako zdefiniowane w implementacji to na ogół te, w których użytkownik może podejmować znaczące decyzje dotyczące kodowania na podstawie definicji implementacji. Realizatorzy powinni wziąć pod uwagę to kryterium przy podejmowaniu decyzji o tym, jak szeroka powinna być definicja implementacji. Podobnie jak w przypadku nieokreślonego zachowania, zwykła niemożność przetłumaczenia źródła zawierającego zachowanie zdefiniowane w implementacji nie jest odpowiednią odpowiedzią.

Johannes Schaub - litb
źródło
3
Hipernowoczesni autorzy kompilatorów uważają również „niezdefiniowane zachowanie” za udzielenie licencji autorom kompilatorów na założenie, że programy nigdy nie otrzymają danych wejściowych, które spowodowałyby Niezdefiniowane zachowanie, oraz do arbitralnej zmiany wszystkich aspektów zachowania programów po otrzymaniu takich danych wejściowych.
supercat
2
Kolejny punkt, który właśnie zauważyłem: C89 nie użył terminu „rozszerzenie” do opisania funkcji, które były gwarantowane w niektórych implementacjach, ale nie w innych. Autorzy C89 uznali, że większość ówczesnych implementacji traktowałaby arytmetykę podpisaną i arytmetykę niepodpisaną identycznie, z wyjątkiem sytuacji, w których wyniki były wykorzystywane w określony sposób, a takie traktowanie było stosowane nawet w przypadku podpisanego przelewu; nie wymienili tego jednak jako powszechnego rozszerzenia w załączniku J2, co sugeruje mi, że postrzegali go jako naturalny stan rzeczy, a nie rozszerzenie.
supercat
10

Niezdefiniowane zachowanie vs. Nieokreślone zachowanie ma krótki opis.

Ich końcowe podsumowanie:

Podsumowując, nieokreślone zachowanie jest zwykle czymś, o co nie powinieneś się martwić, chyba że twoje oprogramowanie musi być przenośne. I odwrotnie, niezdefiniowane zachowanie jest zawsze niepożądane i nigdy nie powinno wystąpić.

Anders Abel
źródło
1
Istnieją dwa rodzaje kompilatorów: te, które, chyba że wyraźnie udokumentowane inaczej, interpretują większość form Niezdefiniowanego Zachowania Standardu jako polegające na charakterystycznych zachowaniach udokumentowanych przez podstawowe środowisko oraz te, które domyślnie tylko użytecznie ujawniają zachowania, które Norma charakteryzuje jako Zdefiniowane w implementacji. Korzystając z kompilatorów pierwszego typu, wiele rzeczy pierwszego typu można wykonać wydajnie i bezpiecznie za pomocą UB. Kompilatory drugiego typu będą odpowiednie do takich zadań, tylko jeśli zapewnią opcje gwarantujące zachowanie w takich przypadkach.
supercat
8

Historycznie zarówno zachowanie zdefiniowane w implementacji, jak i zachowanie nieokreślone reprezentowały sytuacje, w których autorzy standardu oczekiwali, że osoby piszące implementacje wysokiej jakości wykorzystają osąd, aby zdecydować, jakie gwarancje behawioralne, jeśli takie, będą przydatne dla programów w zamierzonym polu aplikacji działających na zamierzone cele. Potrzeby wysokiej klasy kodu do łamania liczb są zupełnie inne niż w przypadku kodów systemów niskiego poziomu, a zarówno UB, jak i IDB dają autorom kompilatorów elastyczność w zaspokajaniu tych różnych potrzeb. Żadna z kategorii nie nakazuje, aby implementacje zachowywały się w sposób przydatny do określonego celu, a nawet do dowolnego celu. Wdrożenia jakościowe, które twierdzą, że są odpowiednie do określonego celu, powinny jednak zachowywać się w sposób odpowiadający temu celowiczy standard tego wymaga, czy nie .

Jedyna różnica między zachowaniem zdefiniowanym w implementacji a zachowaniem niezdefiniowanym polega na tym, że ten pierwszy wymaga, aby implementacje definiowały i dokumentowały spójne zachowanie, nawet w przypadkach, w których wdrożenie nic nie mogłoby być przydatne . Linia podziału między nimi nie polega na tym, czy generalnie przydatne byłyby implementacje w celu zdefiniowania zachowań (autorzy kompilatora powinni zdefiniować użyteczne zachowania, gdy jest to praktyczne, czy standard tego wymaga, czy też nie), ale czy mogą istnieć implementacje, w których zdefiniowanie zachowania byłoby jednocześnie kosztowne i bezużyteczne . Ocena, że ​​takie implementacje mogą istnieć, w żaden sposób, nie kształtuje ani nie formułuje, nie sugeruje żadnej oceny przydatności wspierania określonego zachowania na innych platformach.

Niestety, od połowy lat 90. autorzy kompilatorów zaczęli interpretować brak mandatów behawioralnych jako osąd, że gwarancje behawioralne nie są warte kosztów nawet w obszarach zastosowań, w których są one niezbędne, a nawet w systemach, w których praktycznie nic nie kosztują. Zamiast traktować UB jako zaproszenie do rozsądnego osądu, autorzy kompilatorów zaczęli traktować to jako wymówkę, by tego nie robić.

Na przykład biorąc pod uwagę następujący kod:

int scaled_velocity(int v, unsigned char pow)
{
  if (v > 250)
    v = 250;
  if (v < -250)
    v = -250;
  return v << pow;
}

implementacja uzupełnienia dwójkowego nie musiałaby podejmować żadnego wysiłku, aby traktować wyrażenie v << powjako przesunięcie uzupełnienia dwójkowego bez względu na to, czy vbyło ono pozytywne czy negatywne.

Preferowana filozofia niektórych współczesnych autorów kompilatorów sugerowałaby jednak, że ponieważ vmoże być negatywna tylko wtedy, gdy program zamierza zaangażować się w Niezdefiniowane Zachowanie, nie ma powodu, aby program przycinał ujemny zakres v. Mimo że przesunięcie w lewo wartości ujemnych było obsługiwane na każdym kompilatorze znaczenia, a duża ilość istniejącego kodu opiera się na tym zachowaniu, współczesna filozofia zinterpretowałaby fakt, że Standard mówi, że przesunięcie w lewo wartości ujemnych jest UB jako sugerując, że autorzy kompilatorów powinni swobodnie to zignorować.

supercat
źródło
Ale miłe obchodzenie się z niezdefiniowanym zachowaniem nie przychodzi za darmo. Głównym powodem, dla którego współczesne kompilatory wykazują tak dziwne zachowanie w niektórych przypadkach UB, jest to, że nieustannie optymalizują i aby wykonać najlepszą pracę, muszą być w stanie założyć, że UB nigdy nie występuje.
Tom Swirly,
Ale fakt, że <<jest UB na liczbach ujemnych, jest paskudną małą pułapką i cieszę się, że mi o tym przypominają!
Tom Swirly,
1
@TomSwirly: Niestety, autorzy kompilatorów nie dbają o to, że oferowanie luźnych gwarancji behawioralnych wykraczających poza te nakazane przez Standard może często pozwolić na znaczne zwiększenie prędkości w porównaniu z wymaganiem, aby kod za wszelką cenę unikał wszystkiego, co nie jest zdefiniowane przez Standard. Jeśli programista nie dba o to, czy i+j>kdaje 1, czy 0 w przypadkach, gdy dodatek się przepełnia, pod warunkiem, że nie ma innych skutków ubocznych , kompilator może być w stanie dokonać pewnych masowych optymalizacji, które nie byłyby możliwe, gdyby programista napisał kod jako (int)((unsigned)i+j) > k.
supercat
1
@TomSwirly: Jeśli dla nich kompilator X może podjąć ściśle zgodny program, aby wykonać jakieś zadanie T i uzyskać plik wykonywalny, który jest o 5% bardziej wydajny niż kompilator Y dałby ten sam program, oznacza to, że X jest lepszy, nawet jeśli Y mógłby wygenerować kod, który wykonał to samo zadanie trzy razy wydajniej, biorąc pod uwagę program wykorzystujący zachowania gwarantowane przez Y, ale X tego nie robi.
supercat
6

Standard C ++ n3337 § 1.3.10 zachowanie zdefiniowane w implementacji

zachowanie, w przypadku poprawnie sformułowanego programu konstruuj i poprawiaj dane, które zależą od implementacji i tego, że każda implementacja dokumentuje

Czasami C ++ Standard nie narzuca określonego zachowania niektórym konstruktom, ale zamiast tego mówi, że konkretne, dobrze zdefiniowane zachowanie musi zostać wybrane i opisane przez konkretną implementację (wersję biblioteki). Dzięki temu użytkownik nadal może dokładnie wiedzieć, jak zachowa się program, nawet jeśli Standard tego nie opisuje.


Standard C ++ n3337 § 1.3.24 nieokreślone zachowanie

zachowanie, w stosunku do którego niniejszy standard międzynarodowy nie nakłada żadnych wymagań [Uwaga: Można oczekiwać nieokreślonego zachowania, gdy ten standard międzynarodowy pominie jakąkolwiek wyraźną definicję zachowania lub gdy program użyje błędnej konstrukcji lub błędnych danych. Dopuszczalne niezdefiniowane zachowanie obejmuje od całkowitego zignorowania sytuacji z nieprzewidzianymi rezultatami, zachowania podczas tłumaczenia lub wykonywania programu w udokumentowany sposób charakterystyczny dla środowiska (z wydaniem komunikatu diagnostycznego lub bez niego), aż do zakończenia tłumaczenia lub wykonania (z wydaniem komunikatu diagnostycznego). Wiele błędnych konstrukcji programów nie powoduje nieokreślonego zachowania; należy je zdiagnozować. - uwaga końcowa]

Gdy program napotka konstrukcję, która nie jest zdefiniowana zgodnie ze standardem C ++, można robić, co chce (może wysłać do mnie wiadomość e-mail lub wiadomość e-mail lub całkowicie zignorować kod).


Standard C ++ n3337 § 1.3.25 nieokreślone zachowanie

zachowanie, dla poprawnie sformułowanej konstrukcji programu i poprawnych danych, które zależą od implementacji [Uwaga: Implementacja nie jest wymagana do udokumentowania zachowań. Zakres możliwych zachowań jest zwykle określony przez ten Międzynarodowy Standard. - uwaga końcowa]

Standard C ++ nie narzuca określonych zachowań niektórym konstruktom, ale zamiast tego mówi, że konkretne, dobrze zdefiniowane zachowania muszą być wybrane ( bot nie jest koniecznie opisany ) przez określoną implementację (wersję biblioteki). Tak więc w przypadku, gdy nie podano opisu, może być trudno użytkownikowi dokładnie wiedzieć, jak zachowa się program.

4 szt. 0
źródło
6

Wdrożenie zdefiniowane

Wdrażający chcą, powinien być dobrze udokumentowany, standard daje wybory, ale na pewno się skompiluje

Nieokreślony -

Taki sam jak zdefiniowany w implementacji, ale nieudokumentowany

Nieokreślony-

Cokolwiek może się zdarzyć, zajmij się tym.

Suraj K. Thomas
źródło
2
Myślę, że należy zauważyć, że praktyczne znaczenie „niezdefiniowanego” zmieniło się w ciągu ostatnich kilku lat. Kiedyś tak było, biorąc pod uwagę uint32_t s;, 1u<<skiedy s33 ma być może dać 0, a może 2, ale nie robić nic dziwnego. Nowsze kompilatory, jednak ocena 1u<<smoże spowodować, że kompilator ustali, że ponieważ wcześniej smusiał mieć mniej niż 32, dowolny kod przed lub po tym wyrażeniu, który byłby istotny tylko, gdyby smiał 32 lub więcej, może zostać pominięty.
supercat 16.04.15