6.2.2. Zamówienie ewaluacyjne [expr. Ocena] w języku programowania C ++ tak. Nie mam żadnych innych odniesień
yesraaj
4
Ma rację .. właśnie spojrzał na 6.2.2 w języku programowania C ++ i mówi, że v [i] = i ++ jest niezdefiniowany
dancavallaro,
4
Wyobrażam sobie, ponieważ program kompilujący wykonuje i ++ przed lub po obliczeniu lokalizacji pamięci v [i]. jasne, zawsze będę tam przydzielony. ale może pisać do v [i] lub v [i + 1] w zależności od kolejności operacji.
Evan Teran
2
Wszystko, co mówi język programowania C ++ to: „Kolejność operacji podwyrażeń w wyrażeniu jest niezdefiniowana. W szczególności nie można założyć, że wyrażenie jest oceniane od lewej do prawej”.
dancavallaro,
Odpowiedzi:
233
Wskaźnik
Dereferencje NULLwskaźnika
Dereferencje wskaźnika zwróconego przez „nowy” przydział wielkości zero
Używanie wskaźników do obiektów, których okres użytkowania dobiegł końca (na przykład układanie obiektów przydzielonych lub usuwanie obiektów)
Dereferencje wskaźnika, który nie został jeszcze zdecydowanie zainicjowany
Wykonywanie arytmetyki wskaźnika, która daje wynik poza granicami (powyżej lub poniżej) tablicy.
Dereferencje wskaźnika w miejscu poza końcem tablicy.
Konwertowanie wskaźników na obiekty niezgodnych typów
Odczyt lub zapis do obiektu lub tablicy z przesunięciem ujemnym lub większym niż rozmiar tego obiektu (przepełnienie stosu / sterty)
Przepełnienia liczb całkowitych
Przepełnienie ze znakiem całkowitym
Ocena wyrażenia, które nie jest zdefiniowane matematycznie
Wartości przesunięcia w lewo o wartość ujemną (przesunięcia w prawo o wartości ujemne są zdefiniowane w realizacji)
Przesunięcie wartości o wartość większą lub równą liczbie bitów w liczbie (np. int64_t i = 1; i <<= 72Jest niezdefiniowana)
Rodzaje, obsada i Const
Rzucanie wartości liczbowej na wartość, która nie może być reprezentowana przez typ docelowy (bezpośrednio lub przez static_cast)
Korzystanie ze zmiennej automatycznej przed jej definitywnym przypisaniem (np. int i; i++; cout << i;)
Wykorzystanie wartości dowolnego obiektu typu innego niż volatilelub sig_atomic_tprzy odbiorze sygnału
Próba modyfikacji literału łańcuchowego lub dowolnego innego obiektu const w trakcie jego życia
Łączenie wąskiego z szerokim dosłownym ciągiem podczas wstępnego przetwarzania
Funkcja i szablon
Nie zwraca wartości z funkcji zwracającej wartość (bezpośrednio lub przez odpływ z bloku try)
Wiele różnych definicji tego samego obiektu (klasa, szablon, wyliczenie, funkcja wstawiana, funkcja członka statycznego itp.)
Nieskończona rekurencja w tworzeniu szablonów
Wywołanie funkcji przy użyciu różnych parametrów lub powiązanie z parametrami i powiązaniem, które funkcja jest zdefiniowana jako używanie.
OOP
Kaskadowe niszczenie obiektów o statycznym czasie przechowywania
Wynik przypisania do częściowo nakładających się obiektów
Rekurencyjne ponowne wprowadzanie funkcji podczas inicjalizacji jej obiektów statycznych
Wywoływanie funkcji wirtualnych do czysto wirtualnych funkcji obiektu z jego konstruktora lub destruktora
Odnosząc się do niestatycznych elementów obiektów, które nie zostały zbudowane lub zostały już zniszczone
Plik źródłowy i wstępne przetwarzanie
Niepusty plik źródłowy, który nie kończy się znakiem nowej linii lub kończy się odwrotnym ukośnikiem (wcześniej niż C ++ 11)
Ukośnik odwrotny, po którym następuje znak, który nie jest częścią określonych kodów specjalnych w stałej znaku lub łańcucha (jest to zdefiniowane w C ++ 11).
Przekroczenie limitów implementacji (liczba zagnieżdżonych bloków, liczba funkcji w programie, dostępne miejsce na stosie ...)
Wartości numeryczne preprocesora, które nie mogą być reprezentowane przez long int
Dyrektywa w sprawie przetwarzania wstępnego po lewej stronie definicji makra podobnej do funkcji
Dynamiczne generowanie zdefiniowanego tokena w #ifwyrażeniu
Do sklasyfikowania
Wywołanie wyjścia podczas niszczenia programu o statycznym czasie przechowywania
Hm ... NaN (x / 0) i Infinity (0/0) zostały objęte IEE 754, jeśli C ++ został zaprojektowany później, dlaczego zapisuje x / 0 jako niezdefiniowany?
new123456
Re: „Ukośnik odwrotny, po którym następuje znak, który nie jest częścią określonych kodów specjalnych w stałej znaku lub ciągu”. To jest UB w C89 (§3.1.3.4) i C ++ 03 (który zawiera C89), ale nie w C99. C99 mówi, że „wynik nie jest tokenem i wymagana jest diagnostyka” (§6.4.4.4). Prawdopodobnie C ++ 0x (który zawiera C89) będzie taki sam.
Adam Rosenfield,
1
Standard C99 zawiera listę niezdefiniowanych zachowań w dodatku J.2. Dostosowanie tej listy do C ++ wymagałoby trochę pracy. Trzeba będzie zmienić odniesienia do poprawnych klauzul C ++ zamiast klauzul C99, usunąć wszystko, co nieistotne, a także sprawdzić, czy wszystkie te rzeczy są naprawdę niezdefiniowane zarówno w C ++, jak i C. Ale to daje początek.
Steve Jessop,
1
@ new123456 - nie wszystkie jednostki zmiennoprzecinkowe są kompatybilne z IEE754. Gdyby C ++ wymagało zgodności z IEE754, kompilatory musiałyby przetestować i obsłużyć przypadek, w którym RHS wynosi zero, poprzez jawne sprawdzenie. Poprzez niezdefiniowanie tego zachowania kompilator może uniknąć tego narzutu, mówiąc „jeśli użyjesz FPU innego niż IEE754, nie uzyskasz zachowania IEEE754 FPU”.
SecurityMatt
1
„Ocena wyrażenia, którego wynik nie mieści się w zakresie odpowiednich typów” .... Przepełnienie liczb całkowitych jest dobrze zdefiniowane dla NIEZALEŻNYCH typów całkowych, tylko tych niepodpisanych.
nacitar sevaht
31
Kolejność oceny parametrów funkcji jest nieokreślonym zachowaniem . (Nie spowoduje to awarii programu, wybuchu ani zamawiania pizzy ... w przeciwieństwie do niezdefiniowanego zachowania ).
Jedynym wymaganiem jest to, że wszystkie parametry muszą zostać w pełni ocenione przed wywołaniem funkcji.
To:
// The simple obvious one.
callFunc(getA(),getB());
Może być równoważne z tym:
int a = getA();int b = getB();
callFunc(a,b);
Albo to:
int b = getB();int a = getA();
callFunc(a,b);
Może być albo; to zależy od kompilatora. Wynik może mieć znaczenie, w zależności od skutków ubocznych.
Zamówienie jest nieokreślone, nie jest niezdefiniowane.
Rob Kennedy,
1
Nienawidzę tego :) Straciłem cały dzień pracy, kiedy wyśledziłem jeden z tych przypadków ... w każdym razie nauczyłem się mojej lekcji i na szczęście nie upadłem ponownie
Robert Gould
2
@Rob: Kłóciłbym się z tobą o zmianę znaczenia tutaj, ale wiem, że komitet normalizacyjny jest bardzo wybredny co do dokładnej definicji tych dwóch słów. Więc po prostu to zmienię :-)
Martin York,
2
Mam szczęście w tej sprawie. Ukąsiłem go, gdy byłem na studiach i miałem profesora, który rzucił na to okiem i powiedział mi o moim problemie za około 5 sekund. Nie mówię, ile czasu zmarnowałbym na debugowanie w przeciwnym razie.
Bill the Lizard,
27
Kompilator może dowolnie zamawiać części wyrażenia ewaluacyjnego (zakładając, że znaczenie nie ulegnie zmianie).
Z pierwotnego pytania:
a[i]= i++;// This expression has three parts:(a) a[i](b) i++(c)Assign(b) to (a)// (c) is guaranteed to happen after (a) and (b)// But (a) and (b) can be done in either order.// See n2521 Section 5.17// (b) increments i but returns the original value.// See n2521 Section 5.2.6// Thus this expression can be written as:int rhs = i++;int lhs&= a[i];
lhs = rhs;// orint lhs&= a[i];int rhs = i++;
lhs = rhs;
Blokada podwójnie sprawdzona. I jeden łatwy do popełnienia błąd.
A* a =new A("plop");// Looks simple enough.// But this can be split into three parts.(a) allocate Memory(b)Call constructor
(c)Assign value to 'a'// No problem here:// The compiler is allowed to do this:(a) allocate Memory(c)Assign value to 'a'(b)Call constructor.// This is because the whole thing is between two sequence points.// So what is the big deal.// Simple Double checked lock. (I know there are many other problems with this).if(a == null)// (Point B){Lock lock(mutex);if(a == null){
a =new A("Plop");// (Point A).}}
a->doStuff();// Think of this situation.// Thread 1: Reaches point A. Executes (a)(c)// Thread 1: Is about to do (b) and gets unscheduled.// Thread 2: Reaches point B. It can now skip the if block// Remember (c) has been done thus 'a' is not NULL.// But the memory has not been initialized.// Thread 2 now executes doStuff() on an uninitialized variable.// The solution to this problem is to move the assignment of 'a'// To the other side of the sequence point.if(a == null)// (Point B){Lock lock(mutex);if(a == null){
A* tmp =new A("Plop");// (Point A).
a = tmp;}}
a->doStuff();// Of course there are still other problems because of C++ support for// threads. But hopefully these are addresses in the next standard.
Ooh ... to paskudne, szczególnie odkąd widziałem tę dokładną strukturę zalecaną w Javie
Tom
Zauważ, że niektóre kompilatory definiują zachowanie w tej sytuacji. Na przykład w VC ++ 2005+, jeśli a jest niestabilny, potrzebne są bariery pamięci, aby zapobiec zmianie kolejności instrukcji, dzięki czemu działa podwójne sprawdzanie blokowania.
Eclipse
Martin York: <i> // (c) ma miejsce po (a) i (b) </i> Czy tak jest? Wprawdzie w tym konkretnym przykładzie jedynym scenariuszem, w którym mogłoby mieć znaczenie, byłoby, gdyby „i” było zmienną zmienną zamapowaną na rejestr sprzętowy, a [i] (stara wartość „i”) została do niej przypisana, ale czy istnieje jakikolwiek zagwarantować, że przyrost nastąpi przed punktem sekwencyjnym?
supercat
5
Moim ulubionym jest „nieskończona rekurencja w tworzeniu instancji szablonów”, ponieważ uważam, że jest to jedyny przypadek, w którym niezdefiniowane zachowanie występuje w czasie kompilacji.
Zrobiłem to wcześniej, ale nie rozumiem, jak to jest niezdefiniowane. To całkiem oczywiste, że dokonujesz nieskończonej rekurencji po namyśle.
Robert Gould
Problem polega na tym, że kompilator nie może zbadać twojego kodu i zdecydować, czy będzie cierpieć z powodu nieskończonej rekurencji, czy nie. Jest to przykład problemu z zatrzymaniem. Zobacz: stackoverflow.com/questions/235984/...
Daniel Earwicker
Tak, to zdecydowanie problem z zatrzymaniem
Robert Gould,
spowodował awarię mojego systemu z powodu wymiany spowodowanej zbyt małą pamięcią.
Johannes Schaub - litb
2
Stałe preprocesora, które nie pasują do int, to także czas kompilacji.
Joshua,
5
Przypisywanie do stałej po constrozebraniu za pomocą const_cast<>:
constint i =10;int*p =const_cast<int*>(&i );*p =1234;//Undefined
Oprócz nieokreślonego zachowania istnieje również równie nieprzyjemne zachowanie zdefiniowane w implementacji .
Niezdefiniowane zachowanie występuje, gdy program robi coś, czego wynik nie jest określony przez standard.
Zachowanie zdefiniowane w implementacji jest działaniem programu, którego wynik nie jest zdefiniowany w standardzie, ale wdrożenie musi udokumentować. Przykładem są „Wielobajtowe literały znaków” z pytania Przepełnienie stosu Czy istnieje kompilator C, który tego nie skompilował? .
Zachowanie zdefiniowane w implementacji gryzie cię dopiero po rozpoczęciu przenoszenia (ale uaktualnianie do nowej wersji kompilatora również się przenosi!)
Dotrzyj przynajmniej raz między dwoma punktami sekwencji.
Prasoon Saurav
2
@Prasoon: Myślę, że miałeś na myśli: najwyżej raz między dwoma punktami sekwencji. :-)
Nawaz
3
Podstawowe zrozumienie różnych ograniczeń środowiskowych. Pełna lista znajduje się w sekcji 5.2.4.1 specyfikacji C. Tu jest kilka;
127 parametrów w definicji jednej funkcji
127 argumentów w jednym wywołaniu funkcji
127 parametrów w jednej definicji makra
127 argumentów w jednym wywołaniu makra
4095 znaków w logicznej linii źródłowej
4095 znaków w dosłownym ciągu znaków lub w dosłownym ciągu znaków (po konkatenacji)
65535 bajtów w obiekcie (tylko w środowisku hostowanym)
15 poziomów testowania dla plików zawartych w pakiecie
1023 etykiety przypadków dla instrukcji zamiany (z wyłączeniem etykiet dla jakichkolwiek nowych instrukcji zamiany)
Byłem trochę zaskoczony limitem 1023 etykiet przypadków dla instrukcji switch. Mogę zauważyć, że przekroczenie generowanego kodu / lex / parserów jest dość łatwe.
Jeśli limity te zostaną przekroczone, masz niezdefiniowane zachowanie (awarie, wady bezpieczeństwa itp.).
Tak, wiem, że pochodzi ze specyfikacji C, ale C ++ udostępnia te podstawowe funkcje obsługi.
2 / Funkcja memcpy kopiuje n znaków z obiektu wskazywanego przez s2 do obiektu wskazywanego przez s1. Jeśli kopiowanie odbywa się między nakładającymi się obiektami, zachowanie jest niezdefiniowane. Zwraca 3 Funkcja memcpy zwraca wartość s1.
2 Funkcja memmove kopiuje n znaków z obiektu wskazywanego przez s2 do obiektu wskazywanego przez s1. Kopiowanie odbywa się tak, jakby n znaków z obiektu wskazanego przez s2 zostało najpierw skopiowanych do tymczasowej tablicy n znaków, która nie zachodzi na obiekty wskazane przez s1 i s2, a następnie n znaków z tablicy tymczasowej jest skopiowanych do obiekt wskazywany przez s1. Zwroty
Czy nie po to jest <cstdint>? Definiuje typy, takie jak uint16_6 i tak dalej.
Jasper Bekkers,
Tak, ale rozmiar większości typów, powiedzmy długi, nie jest dobrze zdefiniowany.
JaredPar,
także cstdint nie jest jeszcze częścią obecnego standardu c ++. zobacz boost / stdint.hpp, aby znaleźć obecnie przenośne rozwiązanie.
Evan Teran,
To nie jest niezdefiniowane zachowanie. Norma mówi, że platforma zgodna definiuje rozmiary, a nie norma definiująca je.
Daniel Earwicker,
1
@JaredPar: To złożony post z wieloma wątkami, więc podsumowałem to tutaj . Najważniejsze jest to: „5. Aby reprezentować -2147483647 i +2147483647 w formacie binarnym, potrzebujesz 32 bitów”.
John Dibling
2
Obiekty na poziomie przestrzeni nazw w różnych jednostkach kompilacji nigdy nie powinny zależeć od siebie przy inicjalizacji, ponieważ ich kolejność inicjowania jest niezdefiniowana.
Odpowiedzi:
Wskaźnik
NULL
wskaźnikamemcpy
do kopiowania nakładających się buforów .Przepełnienie bufora
Przepełnienia liczb całkowitych
int64_t i = 1; i <<= 72
Jest niezdefiniowana)Rodzaje, obsada i Const
int i; i++; cout << i;
)volatile
lubsig_atomic_t
przy odbiorze sygnałuFunkcja i szablon
OOP
Plik źródłowy i wstępne przetwarzanie
long int
#if
wyrażeniuDo sklasyfikowania
źródło
Kolejność oceny parametrów funkcji jest nieokreślonym zachowaniem . (Nie spowoduje to awarii programu, wybuchu ani zamawiania pizzy ... w przeciwieństwie do niezdefiniowanego zachowania ).
Jedynym wymaganiem jest to, że wszystkie parametry muszą zostać w pełni ocenione przed wywołaniem funkcji.
To:
Może być równoważne z tym:
Albo to:
Może być albo; to zależy od kompilatora. Wynik może mieć znaczenie, w zależności od skutków ubocznych.
źródło
Kompilator może dowolnie zamawiać części wyrażenia ewaluacyjnego (zakładając, że znaczenie nie ulegnie zmianie).
Z pierwotnego pytania:
Blokada podwójnie sprawdzona. I jeden łatwy do popełnienia błąd.
źródło
Moim ulubionym jest „nieskończona rekurencja w tworzeniu instancji szablonów”, ponieważ uważam, że jest to jedyny przypadek, w którym niezdefiniowane zachowanie występuje w czasie kompilacji.
źródło
Przypisywanie do stałej po
const
rozebraniu za pomocąconst_cast<>
:źródło
Oprócz nieokreślonego zachowania istnieje również równie nieprzyjemne zachowanie zdefiniowane w implementacji .
Niezdefiniowane zachowanie występuje, gdy program robi coś, czego wynik nie jest określony przez standard.
Zachowanie zdefiniowane w implementacji jest działaniem programu, którego wynik nie jest zdefiniowany w standardzie, ale wdrożenie musi udokumentować. Przykładem są „Wielobajtowe literały znaków” z pytania Przepełnienie stosu Czy istnieje kompilator C, który tego nie skompilował? .
Zachowanie zdefiniowane w implementacji gryzie cię dopiero po rozpoczęciu przenoszenia (ale uaktualnianie do nowej wersji kompilatora również się przenosi!)
źródło
Zmienne mogą być aktualizowane tylko raz w wyrażeniu (technicznie raz między punktami sekwencji).
źródło
Podstawowe zrozumienie różnych ograniczeń środowiskowych. Pełna lista znajduje się w sekcji 5.2.4.1 specyfikacji C. Tu jest kilka;
Byłem trochę zaskoczony limitem 1023 etykiet przypadków dla instrukcji switch. Mogę zauważyć, że przekroczenie generowanego kodu / lex / parserów jest dość łatwe.
Jeśli limity te zostaną przekroczone, masz niezdefiniowane zachowanie (awarie, wady bezpieczeństwa itp.).
Tak, wiem, że pochodzi ze specyfikacji C, ale C ++ udostępnia te podstawowe funkcje obsługi.
źródło
Używanie
memcpy
do kopiowania między nakładającymi się regionami pamięci. Na przykład:Zachowanie jest niezdefiniowane zgodnie ze standardem C, który jest objęty standardem C ++ 03.
7.21.2.1 Funkcja memcpy
7.21.2.2 Funkcja memmove
źródło
Jedynym typem, dla którego C ++ gwarantuje rozmiar, jest
char
. Rozmiar to 1. Rozmiar wszystkich innych typów zależy od platformy.źródło
Obiekty na poziomie przestrzeni nazw w różnych jednostkach kompilacji nigdy nie powinny zależeć od siebie przy inicjalizacji, ponieważ ich kolejność inicjowania jest niezdefiniowana.
źródło