Pytanie Czy wyrażenie o niezdefiniowanym zachowaniu, które nigdy nie jest faktycznie wykonywane, powoduje, że program jest błędny?


W wielu dyskusjach na temat niezdefiniowanego zachowania (UB), punkt widzenia został przedstawiony jako zwykły obecność w programie dowolnego konstruktu, który ma UB w programie, wymaga zgodnej implementacji, aby zrobić cokolwiek (w tym w ogóle nic). Moje pytanie dotyczy tego, czy powinno to być podejmowane w tym sensie, nawet w tych przypadkach, w których UB jest powiązane z wykonanie kodu, podczas gdy zachowanie (inaczej) określone w normie stanowi, że dany kod nie powinien być wykonywany (i to może dla konkretnego wejścia do programu, może nie być rozstrzygalny w czasie kompilacji).

Wyrażone bardziej nieoficjalnie, czy zapach UB wymaga zgodnej implementacji, aby zdecydować, że cały program śmierdzi, i odmawia wykonania poprawnie nawet części programu, dla których zachowanie jest doskonale zdefiniowane. Przykładowy program będzie

#include <iostream>

int main()
{
    int n = 0;
    if (false)
      n=n++;   // Undefined behaviour if it gets executed, which it doesn't
    std::cout << "Hi there.\n";
}

Dla jasności, Zakładam, że program jest dobrze sformułowany (w szczególności UB nie jest związany z preprocesingiem). W rzeczywistości jestem skłonny ograniczyć się do UB powiązanego z "ocenami", które w oczywisty sposób nie są obiektami kompilacyjnymi. Definicje odnoszące się do podanego przykładu są, moim zdaniem, (nacisk jest mój):

Sekwencjonowane wcześniej jest asymetryczną, przechodnią, parą relacji między wykonane oceny przez pojedynczy wątek (1.10), który indukuje częściowy porządek wśród tych ocen

Obliczenia wartości operandów   Operator jest sekwencjonowany przed obliczeniem wartości wyniku operatora. Jeśli efekt uboczny na obiekcie skalarnym jest niesekwencjonowana w stosunku do ... lub obliczanie wartości używając wartości tego samego obiektu skalarnego, zachowanie jest niezdefiniowane.

Jest oczywiste, że podmioty w ostatnim zdaniu, "efekt uboczny" i "obliczenie wartości", są przypadkami "oceny", ponieważ to właśnie definiuje relację "zsekwencjonowana wcześniej".

Zakładam, że w powyższym programie norma przewiduje, że nie ma żadnych ocen, dla których warunek w ostatnim zdaniu jest spełniony (nie jest to względnie względem siebie i opisanego rodzaju) i dlatego program nie ma UB; to nie jest błędne.

Innymi słowy jestem przekonany, że odpowiedź na pytanie o mój tytuł jest negatywna. Byłbym jednak wdzięczny (umotywowane) opiniom innych osób w tej sprawie.

Może dodatkowe pytanie dla tych, którzy opowiadają się za twierdzącą odpowiedzią, brzmiałoby, że przysłowiowe formatowanie dysku twardego może wystąpić, gdy zostanie skompilowany błędny program?

Niektóre powiązane wskaźniki na tej stronie:


18
2018-06-12 14:15


pochodzenie


Ścieżki nieużywane nie mogą wywoływać niezdefiniowanego zachowania; myślenie, że w przeciwnym razie prowadzi do szaleństwa ... Przykładem może być każdy test zerowy, a następnie dereferencja. Zobacz też stackoverflow.com/questions/7961067 - Nemo
ta odpowiedź i komentarze Mówią, że cały program jest zepsuty, niezależnie od tego, czy część, która powoduje UB, wykonuje, czy też nie. - BЈовић
@ BЈовић Ta odpowiedź jest po prostu błędna. - n.m.
@TonyD Wyjaśnienie: Odpowiedź Pete'a Beckera jest błędna w odniesieniu do pytania dotyczącego nigdy nie zrealizowanego kodu (jeśli w ogóle dotyczy tego pytania). Jeśli nieprawidłowy kod zostanie kiedykolwiek wykonany, oczywiście musi zostać naprawiony, ale nie o to właśnie mówimy. - n.m.
Odpowiedź Pete'a Beckera jest sama w sobie słuszna, ale komentarz powyżej @ BЈовић jest błędny, ponieważ Pete nie mówi "bez względu na to, czy powoduje to, co powoduje UB, czy nie". W powiązaniu z pytaniem UB jest nieuchronnie wykonywane, a implementacja ma prawo nie robić tego, co standard przepisuje nawet przed "wykonaniem" UB. Ale to nie jest to samo, co złe zachowanie bez pewności, że UB zostanie wykonane (a nawet pewności, że nie). - Marc van Leeuwen


Odpowiedzi:


Jeśli efekt uboczny na obiekcie skalarnym jest nieodwzorowany w stosunku do itp

Efekty uboczne to zmiany w stanie środowiska wykonawczego (1.9 / 12). Zmiana to zmiana, nie wyrażenie, które w przypadku oceny mogłoby potencjalnie spowodować zmianę. Jeśli nie ma zmian, nie ma efektu ubocznego. Jeśli nie ma efektu ubocznego, to nie ma żadnego efektu ubocznego w stosunku do czegokolwiek innego.

To nie znaczy, że każdy Kod, który nigdy nie jest wykonywany, jest wolny od UB (choć jestem prawie pewien, że jest to większość). Każde wystąpienie UB w normie należy rozpatrywać osobno. (Wypaczony tekst jest prawdopodobnie zbyt ostrożny, patrz poniżej).

Standard mówi tak samo

Dopasowująca implementacja realizująca dobrze uformowany program powinna dawać to samo obserwowalne zachowanie   jako jedno z możliwych wykonań odpowiedniej instancji abstrakcyjnej maszyny z tym samym programem   i to samo wejście. Jednak jeśli takie są wykonanie zawiera niezdefiniowaną operację, tę Międzynarodówkę   Standard nie nakłada żadnych wymagań na implementację wykonującą ten program z tym wejściem (nawet nie   w odniesieniu do operacji poprzedzających pierwszą niezdefiniowaną operację).

(nacisk mój)

Jest to, o ile wiem, jedyne normatywne odniesienie, które mówi, co oznacza "niezdefiniowane zachowanie": niezdefiniowana operacja w wykonanie programu. Brak wykonania, brak UB.


9
2018-06-12 15:02



To zdecydowanie najlepsza odpowiedź. Nadal warto zauważyć, że niezdefiniowane zachowanie, jeśli zostanie wywołane, może "cofać się w czasie" ... Na przykład, jeśli zgodny kompilator może udowodnić twój program zawszewywołuje UB, można skompilować cały program w tryb "no-op". Ale UB musi nadal występować na faktycznie podjętej ścieżce kodu. (Czuję się również zobowiązany to odnotować constexpr i UB mają zabawne interakcje w C ++ 11/14 / xxx, których nie jestem pewien, czy kiedykolwiek w pełni zrozumiem.) - Nemo
"Wykonanie zgodne z wykonywaniem dobrze uformowany program "może wskazywać, że dla" niezupełnie "uformowanych programów wszystkie zakłady są wyłączone. - BЈовић
@ BЈовић Standard mówi, które programy są źle sformułowane. Niektóre z nich wymagają diagnostyki, inne nie. W tej ostatniej kategorii wszystkie zakłady są rzeczywiście wyłączone, standard mówi tak wyraźnie. - n.m.
@Nemo: Dotyczące constexpr (dokładniej stałe wyrażenia które mogą lub nie mogą obejmować constexpr słowo kluczowe), te wyrażenia musi być obliczane w czasie kompilacji, ponieważ występują w kontekstach (na przykład w przypadku typu tablicy występującego w strukturze), gdzie kompilator musi znać ich wartość (znając przesunięcie kolejnych pól w przykładzie). Spodziewam się programu, w którym ocena takiego wyrażenia albo nie da stałej kompilacji, albo wywoła (powiedzmy) dzielenie przez 0, by można ją było nazwać źle sformułowaną. Ale nie mam na to gotowego rozdziału i wersetu. - Marc van Leeuwen
@Nemo: Jeśli program odczytuje zmienną typu volatile char a kompilator ustala, że ​​będzie angażował się w UB niezależnie od odczytanej wartości, czy byłby w stanie narysować jakiekolwiek wnioskowania wstecz przed odczytem, ​​czy też musiałby dopuszczać możliwość, że odczyt zmiennej lotnej może wyzwolić sygnał, który uniemożliwia wykonanie egzekucji na tyle daleko, aby dotrzeć do UB? - supercat


No. Przykład:

struct T {
    void f() { }
};
int main() {
    T *t = nullptr;
    if (t) {
        t->f(); // UB if t == nullptr but since the code tested against that
    }
}

6
2018-06-12 15:44



To dobry przykład, ale może być świetny, aby poprawić jakość odpowiedzi, dostarczając wyjaśnienia, odniesienia itp - Manu343726
Odpowiedziałeś "Nie" do czego? Co twój przykład powinien pokazać? Dziwne, ile głosów w górę tak zła odpowiedź. - BЈовић
@ BЈовић "nie" należy traktować jako odpowiedź na pytanie tytułowe: nie powoduje to, że program jest błędny. Przykład jest jasnym przykładem, dlaczego tak musi być, +1. - Marc van Leeuwen


Decydowanie, czy program wykona podział całkowity przez 0 (który jest UB) jest ogólnie równoważne z problemem zatrzymania. Kompilator nie może tego w ogóle ustalić. Tak więc sama obecność możliwego UB nie może logicznie wpłynąć na resztę programu: wymaganie tego efektu w standardzie, wymagałoby od każdego dostawcy kompilatora dostarczenia zatrzymującego rozwiązywanie problemów w kompilatorze.

Jeszcze prostsze, następujący program ma UB tylko wtedy, gdy użytkownik wprowadzi 0:

#include <iostream>
using namespace std;

auto main() -> int
{
    int x;
    if( cin >> x ) cout << 100/x << endl;
}

Absurdem byłoby utrzymywać, że ten program w sobie ma UB.

Jednak po wystąpieniu nieokreślonego zachowania wszystko może się zdarzyć: dalsze wykonanie kodu w programie jest wówczas zagrożone (na przykład stos może zostać sfałdowany).


5
2018-06-12 18:40



Wiem, że nie wszystkie UB można (łatwo) wykryć nawet w czasie działania, a co dopiero decydować przez zwykłą inspekcję programu; właśnie dlatego zachowanie się, jakby nic specjalnego się nie stało, jest jedną z prawnych opcji UB. Ale pytanie brzmi, czy implementacja, która wykrywa, że ​​wyrażenie mogłoby wytworzyć UB, może wymanewrować ten fakt, by bronić nienormalnego zachowania, nawet jeśli wykonanie tego kodu nie ma (lub nie może być udowodnione) zajścia. Odpowiedź brzmi: NIE, wierzę, ale nawet gdyby było TAK, to nie zobowiązać każdy, kto rozwiąże problem z zatrzymaniem. - Marc van Leeuwen
@MarcvanLeeuwen: masz rację. pierwsza część mojej odpowiedzi tutaj jest ... głupia. nie jest prawidłowym argumentem. argh. dzięki, naprawianie. - Cheers and hth. - Alf
Jaki jest cel używania wstecznego typu powrotu na głównym? - Manu343726
@ Manu343726: raczej, jaki jest sens arbitralnego używania dwóch różnych składni deklaracji funkcji. mamy teraz jedną składnię, która obejmuje wszystko. nie ma powodu, żeby jej nie używać. - Cheers and hth. - Alf
To bardzo dobry punkt. Zapamiętam to. - Manu343726


W ogólnym przypadku najlepiej możemy powiedzieć, że to zależy.

Jeden przypadek, w którym odpowiedź brzmi nie, ma miejsce, gdy mamy do czynienia z nieokreślonymi wartościami. Najnowsza wersja wyraźnie to czyni niezdefiniowane zachowanie w celu uzyskania nieokreślonej wartości podczas oceny z pewnymi wyjątkami, ale próbka kodu wyraźnie pokazuje, jak subtelne może być:

[ Przykład:

int f(bool b) {
  unsigned char c;
  unsigned char d = c; // OK, d has an indeterminate value
  int e = d;           // undefined behavior
  return b ? d : 0;    // undefined behavior if b is true
}

- przykład końcowy ]

więc ta linia kodu:

return b ? d : 0;

jest tylko niezdefiniowane, jeśli bjest true. Wydaje się, że jest to podejście intuicyjne i wydaje się, że także John Regehr to widzi, jeśli czytamy Czas zacząć się martwić o wykorzystywanie niezdefiniowanych zachowań.

W tym przypadku odpowiedź brzmi "tak", kod jest błędny, mimo że nie wywołujemy kodu wywołującego niezdefiniowane zachowanie:

constexpr const char *str = "Hello World" ;      

constexpr char access()
{
    return str[100] ;
}

int main()
{
}

clang zdecyduje się zrobić access błędne, nawet jeśli nigdy nie zostanie wywołane (zobacz na żywo).


3
2018-06-12 16:31



jeśli próbka kodu pochodzi ze standardowego projektu, powinna zostać poprawiona. to nigdy nie UB radzi sobie z wartościami bajtów, niezależnie od tego, jak są nieokreślone. ponieważ bajty nie mają nieprawidłowych znaków bitowych. powiązane, prawdopodobnie wszystko przykłady używające wyjścia iostream w C ++ 98 i c ++ 03 były niepoprawne. również przykłady dotyczące UB w wyrażeniach były nieprawidłowe. szczęśliwie są to przykłady nienormatywny. to tylko przykłady. - Cheers and hth. - Alf
@ Cheersandhth.-Alf powinieneś przeczytać Czy standard C ++ został zmieniony w odniesieniu do użycia nieokreślonych wartości i niezdefiniowanego zachowania w C ++ 1y? najnowszy projekt bardzo zmienił język. - Shafik Yaghmour
tak, to właśnie komentowałem. obserwowałem tylko modowe kwadratowe koła. Nie mam ochoty sprawdzać konstrukcji zmodyfikowanych układów elektrycznych w samochodzie itp., dziękuję. - Cheers and hth. - Alf
@ Cheersandhth.-Alf przykłady są zgodne z tekstem normatywnym, o ile mogę to stwierdzić, a tekst jest dość wyraźny i nie pozostawia wiele miejsca na interpretację. - Shafik Yaghmour
ouch, bałem się tego. tak łatwo jest go zniszczyć w porównaniu do tworzenia. :( - Cheers and hth. - Alf


Istnieje wyraźny podział na nieodłączne niezdefiniowane zachowania, takie jak n = n ++, i kod, który może mieć zdefiniowane lub niezdefiniowane zachowanie w zależności od stanu programu w czasie wykonywania, takie jak x / y dla ints. W tym ostatnim przypadku program musi działać, dopóki y nie wynosi 0, ale w pierwszym przypadku kompilator jest proszony o wygenerowanie kodu, który jest całkowicie nielegalny - w jego prawach do odmowy kompilacji, może on po prostu nie być "pociskami odpornymi" na takie kod i, w konsekwencji, stan optymalizatora (przydziały rejestru, rekordy, których wartości mogły zostać zmodyfikowane od czasu odczytu itd.) zostają uszkodzone, co prowadzi do fałszywego kodu maszynowego i otoczenie kod źródłowy. Być może wczesna analiza rozpoznała sytuację "a = b ++" i wygenerowała kod dla poprzedzającego, jeśli przeskoczyłaby instrukcję dwubajtową, ale gdy napotkano n = n ++, żadna instrukcja nie została wyprowadzona, tak, że instrukcja if skoczy gdzieś w kierunku następujące kody opcodes. W każdym razie to po prostu koniec gry. Umieszczenie znaku "jeśli" na przedzie, a nawet zawinięcie go w inną funkcję, nie jest udokumentowane jako "zawierające" niezdefiniowane zachowanie ... fragmenty kodu nie są skażone niezdefiniowanym zachowaniem - Standard konsekwentnie mówi "program ma niezdefiniowane zachowanie ".


3
2018-06-13 01:15



Kiedy mówisz "jest w ramach [kompilatora] prawa do odmowy kompilacji", jest to prawdą w tym sensie, że nie musi emitować żadnego kodu dla instrukcji UB (lub nawet dla jakiejkolwiek ścieżki nieuchronnie wpadnie na UB komunikat). Nie może odmówić skompilowania twojego programu (jeśli jest dobrze uformowany) i na jakimkolwiek wejściu, w którym instrukcja UB nigdy nie byłaby szyfrowana, program powinien zachowywać się jak określony. Na przykład, jeśli właśnie podzieliłeś przez n a następnie powiedzieć if (n==0) something;, kompilator może tłumić to warunkowe, zajmując się tylko sytuacją UB. Nadal będzie działać n!=0. - Marc van Leeuwen
Podsumowując, nie zgadzam się z "po prostu grą zakończoną", w szczególności dla przykładu w pytaniu, w którym nigdy wykonany. - Marc van Leeuwen
Tak - widzę na podstawie pytania, które chcesz tylko przekazać światu własnemu poglądowi ... znokautuj kolegę. - user433534
Nie całkiem. Tak, miałem wątpliwości co do tego, czy ludzie interpretowali wolność przyznawaną na temat UB w zbyt ekstremalny sposób, ponieważ nawet zastosowanie do czysto hipotetycznego UB (jak to robicie) mogłoby mieć rację, a ja liczyłem na argumenty przeciwne, ale ja miałem prawdziwe pytanie w tym czasie. Odpowiedzi, które otrzymałem (na przykład wyjątek) dostarczyły przekonującego argumentu, na który mam nadzieję, więc teraz nie mam już takich wątpliwości. - Marc van Leeuwen


Powinno być, jeśli nie "powinien".

Zachowanie, z definicji z ISO C (brak odpowiadającej definicji znalezionej w ISO C ++, ale powinno być nadal w jakiś sposób stosowane), jest:

3.4

1 zachowanie

wygląd zewnętrzny lub działanie

I UB:

WG21 / N4527

1.3.25 [defns.undefined]

niezdefiniowane zachowanie

zachowanie, dla którego niniejszy standard międzynarodowy nie nakłada żadnych wymagań [Uwaga: niezdefiniowane zachowanie można się spodziewać, gdy niniejszy standard międzynarodowy pominie jakąkolwiek wyraźną definicję zachowania lub gdy program użyje błędnej konstrukcji lub błędnych danych. Dopuszczalne nieokreślone zachowanie sięga od całkowitego zignorowania sytuacji z nieprzewidywalnymi rezultatami, zachowania podczas tłumaczenia lub wykonania programu w udokumentowany sposób, charakterystyczny dla środowiska (z wydaniem komunikatu diagnostycznego lub bez), do zakończenia tłumaczenia lub wykonania (z wydaniem komunikatu diagnostycznego). Wiele błędnych konstrukcji programu nie wywołuje niezdefiniowanych zachowań; są one wymagane do zdiagnozowania.   -End note]

Pomimo "zachowania się podczas tłumaczenia" powyżej, słowo "zachowanie" używane przez ISO C ++ dotyczy głównie języka wykonanie programów.

WG21 / N4527

1.9 Realizacja programu [intro.execution]

1 Opisy semantyczne w niniejszym standardzie międzynarodowym definiują sparametryzowaną niedeterministyczną maszynę abstrakcyjną. Ta międzynarodowa norma nie nakłada wymogu na strukturę zgodnych implementacji. W szczególności nie muszą kopiować ani emulować struktury abstrakcyjnej maszyny. Przeciwnie, implementacje zgodne z wymaganiami są wymagane do emulowania (tylko) obserwowalnego zachowania abstrakcyjnej maszyny, jak wyjaśniono poniżej.5

2 Niektóre aspekty i operacje abstrakcyjnej maszyny są opisane w niniejszej Normie Międzynarodowej jako zdefiniowane w ramach implementacji (na przykład sizeof(int)). Stanowią one parametry abstrakcyjnej maszyny.   Każda implementacja zawiera dokumentację opisującą jej charakterystykę i zachowanie w tym względzie.6 Dokumentacja taka definiuje wystąpienie abstrakcyjnej maszyny, która odpowiada tej implementacji (określanej jako "odpowiednia instancja" poniżej).

3 Niektóre inne aspekty i operacje abstrakcyjnej maszyny opisane są w niniejszej Normie Międzynarodowej jako nieokreślone (na przykład, ocena wyrażeń w nowy-inicjator jeśli funkcja alokacji nie może przydzielić pamięci (5.3.4)). Tam, gdzie to możliwe, niniejszy standard międzynarodowy definiuje zbiór dozwolonych zachowań.   Określają one niedeterministyczne aspekty abstrakcyjnej maszyny. Instancja maszyny abstrakcyjnej może zatem mieć więcej niż jedno możliwe wykonanie dla danego programu i danego wejścia.

4 Niektóre inne operacje są opisane w niniejszej Normie Międzynarodowej jako nieokreślone (na przykład skutek próby modyfikacji a const obiekt). [Uwaga: niniejsza Norma Międzynarodowa nie nakłada żadnych wymagań na zachowanie programów, które zawierają niezdefiniowane zachowanie. -End note]

5 Odpowiednia implementacja realizująca dobrze uformowany program powinna dawać takie samo obserwowalne zachowanie, jak jedno z możliwych wykonań odpowiedniej instancji abstrakcyjnej maszyny z tym samym programem i tym samym wpisem. Jeśli jednak takie wykonanie zawiera niezdefiniowaną operację, ta Międzynarodowa Norma 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ę).

5) Przepis ten jest czasem nazywany regułą "jak gdyby", ponieważ implementacja może zignorować wszelkie wymagania niniejszej Normy Międzynarodowej, o ile wynik jest jak gdyby wymaganie to było przestrzegane, o ile można je określić na podstawie obserwowalnego zachowania programu. Na przykład, rzeczywista implementacja nie musi oceniać części wyrażenia, jeśli może wywnioskować, że jego wartość nie jest używana i że nie są generowane żadne efekty uboczne wpływające na obserwowalne zachowanie programu.

6) Ta dokumentacja obejmuje także konstrukcje obsługiwane warunkowo i zachowanie specyficzne dla lokalizacji. Zobacz 1.4.

Oczywiste jest, że niezdefiniowane zachowanie powodowane jest przez konkretną konstrukcję językową używaną niewłaściwie lub w sposób nieprzenośny (co nie jest zgodne ze standardem). Jednak standard nie wspomina nic o tym, która konkretna część kodu w programie mogłaby go spowodować. Innymi słowy, "o niezdefiniowanym zachowaniu" jest właściwością (o dopasowaniu) całego wykonywanego programu, a nie jakiejkolwiek jego mniejszej części.

Norma mogłaby dać silniejszą gwarancję, że zachowanie będzie dobrze zdefiniowane, gdy jakiś konkretny kod nie zostanie wykonany, tylko wtedy, gdy istnieje sposób na dokładne odwzorowanie kodu C ++ na odpowiednie zachowanie. Jest to trudne (jeśli nie niemożliwe) bez szczegółowego semantycznego modelu realizacji. W skrócie, semantyka operacyjna podana przez abstrakcyjny model maszyny nie jest wystarczająca do uzyskania silniejszej gwarancji. W każdym razie ISO C ++ nigdy nie będzie JVMS ani ECMA-335. I nie oczekuję, że będzie kompletny zestaw formalnej semantyki opisującej język.

Kluczowym problemem jest tutaj znaczenie "wykonania". Niektórzy uważają, że "wykonanie programu" oznacza uruchomienie programu. To nie jest do końca prawdą. Zauważ, że reprezentacja programu wykonanego w maszynie abstrakcyjnej nie jest określona. (Zwróć też uwagę, że "niniejszy standard międzynarodowy nie nakłada żadnych wymagań na strukturę implementacji zgodnych".) Kod wykonywany tutaj może być literalnie C ++ (niekoniecznie kod maszynowy lub inne formy pośredniego kodu, który nie jest określony przez standard w ogóle ). Dzięki temu język podstawowy może być implementowany jako interpreter, część oceniająca online lub inne potwory tłumaczące kod C ++ w locie. W rezultacie tak naprawdę nie ma możliwości rozdzielenia faz tłumaczenia (zdefiniowanych przez ISO C ++ [lex.phases]) całkowicie przed procesem wykonania bez wiedzy o konkretnych implementacjach. Dlatego konieczne jest dopuszczenie UB podczas tłumaczenia, gdy zbyt trudno jest określić przenośne, dobrze zdefiniowane zachowanie.

Poza powyższymi problemami, być może dla zwykłych użytkowników, wystarczy jeden (nietechniczny) powód: po prostu niepotrzebne jest zapewnienie silniejszej gwarancji, dopuszczenie złego kodu i pokonanie jednego z (prawdopodobnie najważniejszego) aspektu użyteczności samego UB: aby zachęcić do szybkiego odrzucenia niepotrzebnego (niepotrzebnego) śmierdzącego kodu bez wysiłku, aby je "naprawić", co ostatecznie byłoby daremne.

Dodatkowe uwagi:

Niektóre słowa są kopiowane i rekonstruowane z jednej z moich odpowiedzi ten komentarz.


1
2017-09-07 08:52





Kompilator AC może robić wszystko, co mu się podoba, gdy tylko program przejdzie do stanu, w którym nie ma zdefiniowanej sekwencji zdarzeń, która pozwoliłaby programowi uniknąć wywoływania Nieokreślonego Zachowania w jakimś punkcie w przyszłości (zauważ, że każda pętla, która nie ma wszelkie efekty uboczne, które nie mają warunku wyjścia, który kompilator wymagałby rozpoznania, sam wywołuje Nieokreślone zachowanie). Zachowanie kompilatora w takich przypadkach jest związane prawami ani czasu, ani związku przyczynowego. W sytuacjach, w których występuje niezdefiniowane zachowanie w wyrażeniu, którego wynik nigdy nie jest używany, niektóre kompilatory nie wygenerują żadnego kodu dla wyrażenia (więc nie będzie on nigdy "wykonywać"), ale to nie powstrzyma kompilatorów od używania niezdefiniowanego zachowania do wykonania innych wnioski dotyczące zachowania programu.

Na przykład:

void maybe_launch_missiles(void)
{      
  if (should_launch_missiles())
  {
    arm_missiles();
    if (should_launch_missiles())
      launch_missiles();
  }
  disarm_missiles();
}
int foo(int x)
{
  maybe_launch_missiles();
  return x<<1;
}

Zgodnie z bieżącym C standardem C, jeśli kompilator może to określić disarm_missiles() zawsze powracał bez zakończenia, ale trzy pozostałe funkcje zewnętrzne, które zostały wywołane, mogły zostać zakończone, co jest najbardziej skutecznym zamiennikiem zgodnym ze standardami foo(-1);(wartość zwracana zignorowana) byłaby should_launch_missiles(); arm_missiles(); should_launch_missiles(); launch_missiles();.

Zachowanie programu zostanie zdefiniowane tylko wtedy, gdy zostanie wywołane should_launch_missiles() kończy się bez zwracania, jeśli pierwsze połączenie zwraca wartość niezerową i arm_missiles() kończy się bez zwracania lub jeśli oba wywołania zwracają wartość niezerową i launch_missiles() kończy się bez powrotu. Program, który działa poprawnie w tych przypadkach, będzie zgodny z normą, niezależnie od tego, co robi w każdej innej sytuacji. Jeśli wrócisz z maybe_launch_missiles() spowodowałoby niezdefiniowane zachowanie, kompilator nie musiałby rozpoznać możliwości, do której zadzwoni should_launch_missiles() może zwrócić zero.

W konsekwencji, niektóre nowoczesne kompilatory, efekt przesunięcia lewej liczby ujemnej może być gorszy niż byle co może to być spowodowane jakimkolwiek niezdefiniowanym zachowaniem na typowym kompilatorze C99 na platformach, które oddzielają przestrzenie kodu i danych oraz przepełnienie stosu pułapek. Nawet jeśli kod zaangażowałby się w niezdefiniowane zachowanie, które mogło spowodować losowe transfery sterowania, nie byłoby żadnych środków, które mogłyby spowodować arm_missiles() i launch_missiles() nazywać się po kolei bez interweniowania disarm_missiles() chyba że przynajmniej jedno połączenie should_launch_missiles() zwrócił wartość niezerową. Hiper-nowoczesny kompilator może jednak negować takie zabezpieczenia.


0
2018-04-19 19:04





W kontekście wbudowanego systemu krytycznego pod względem bezpieczeństwa, opublikowany kod uznany zostałby za wadliwy:

  1. Kod nie powinien zawierać przeglądu kodu i / lub zgodności z normami (MISRA itp.)
  2. Analiza statyczna (lint, cppcheck, itp.) Powinna oznaczać to jako defekt
  3. Niektóre kompilatory mogą oznaczać to jako ostrzeżenie (co również oznacza wadę).

-2
2018-06-12 15:23



Niektórych rzeczy nie można sprawdzić za pomocą kompilatorów lub narzędzi do analizy statycznej, a mimo to wywołać UB. To powiedziawszy, nie widzę sposobu, w jaki odpowiedziałeś na to pytanie. - BЈовић
Szczególnie pytam o niezdefiniowane zachowanie, a nie o inne dyskwalifikacje. - Marc van Leeuwen