C ++ 98 i C ++ 03
Ta odpowiedź dotyczy starszych wersji standardu C ++. Wersje standardu C ++ 11 i C ++ 14 nie zawierają formalnie „punktów sekwencji”; zamiast tego operacje są „sekwencjonowane przed” lub „niesekwencjonowane” lub „nieokreślone”. Efekt netto jest zasadniczo taki sam, ale terminologia jest inna.
Uwaga : OK. Ta odpowiedź jest trochę długa. Więc miej cierpliwość podczas czytania. Jeśli już znasz te rzeczy, ich ponowne przeczytanie nie doprowadzi Cię do szaleństwa.
Wymagania wstępne : Podstawowa znajomość standardu C ++
Co to są punkty sekwencji?
Standard mówi
W określonych punktach sekwencji wykonania, zwanych punktami sekwencji , wszystkie skutki uboczne poprzednich ocen będą kompletne i nie będą miały miejsca żadne skutki uboczne kolejnych ocen. (§1,9 / 7)
Skutki uboczne? Jakie są skutki uboczne?
Ocena wyrażenia wytwarza coś, a jeśli dodatkowo nastąpi zmiana stanu środowiska wykonawczego, mówi się, że wyrażenie (jego ocena) ma pewne skutki uboczne.
Na przykład:
int x = y++; //where y is also an int
Oprócz operacji inicjalizacji wartość y
zmienia się również z powodu działania ubocznego ++
operatora.
Na razie w porządku. Przechodzenie do punktów sekwencji. Alternatywna definicja punktów sekwencyjnych podana przez autora comp.lang.c Steve Summit
:
Punkt sekwencyjny to punkt w czasie, w którym kurz osiadł, a wszystkie efekty uboczne, które do tej pory zaobserwowano, są zagwarantowane jako całkowite.
Jakie są wspólne punkty sekwencji wymienione w standardzie C ++?
To są:
na końcu oceny pełnego wyrażenia ( §1.9/16
) (Pełne wyrażenie to wyrażenie, które nie jest podwyrażeniem innego wyrażenia). 1
Przykład:
int a = 5; // ; is a sequence point here
w ocenie każdego z poniższych wyrażeń po ocenie pierwszego wyrażenia ( §1.9/18
) 2
a && b (§5.14)
a || b (§5.15)
a ? b : c (§5.16)
a , b (§5.18)
(tutaj a, b jest operatorem przecinkowym; in func(a,a++)
,
nie jest operatorem przecinkowym, jest jedynie separatorem między argumentami a
i a++
. W związku z tym zachowanie jest niezdefiniowane (jeśli a
jest uważane za typ pierwotny))
w wywołaniu funkcji (bez względu na to, czy funkcja jest wbudowana), po ocenie wszystkich argumentów funkcji (jeśli istnieją), które mają miejsce przed wykonaniem wyrażeń lub instrukcji w treści funkcji ( §1.9/17
).
1: Uwaga: ocena pełnego wyrażenia może obejmować ocenę podwyrażeń, które nie są leksykalnie częścią pełnego wyrażenia. Na przykład podwyrażenia zaangażowane w ocenę domyślnych wyrażeń argumentów (8.3.6) uważa się za utworzone w wyrażeniu wywołującym funkcję, a nie wyrażeniu definiującym domyślny argument
2: Wskazane operatory to wbudowane operatory, jak opisano w klauzuli 5. Gdy jeden z tych operatorów jest przeciążony (klauzula 13) w ważnym kontekście, wyznaczając w ten sposób zdefiniowaną przez użytkownika funkcję operatora, wyrażenie oznacza wywołanie funkcji i operandy tworzą listę argumentów, bez domyślnego punktu sekwencji między nimi.
Co to jest niezdefiniowane zachowanie?
Standard określa niezdefiniowane zachowanie w sekcji §1.3.12
jako
zachowanie, które może powstać w wyniku użycia błędnej konstrukcji programu lub błędnych danych, dla których niniejszy standard międzynarodowy nie nakłada żadnych wymagań 3 .
Nieokreślonego zachowania można się również spodziewać, gdy w niniejszej Normie Międzynarodowej pominięto opis jakiejkolwiek wyraźnej definicji zachowania.
3: dopuszczalne niezdefiniowane zachowanie sięga 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 lub bez komunikatu diagnostycznego), do zakończenia tłumaczenia lub wykonania (z wydaniem komunikatu diagnostycznego).
Krótko mówiąc, niezdefiniowane zachowanie oznacza, że wszystko może się zdarzyć, od demonów wylatujących z nosa po zajście w ciążę przez dziewczynę.
Jaki jest związek między niezdefiniowanym zachowaniem a punktami sekwencji?
Zanim do tego przejdę, musisz poznać różnicę między zachowaniem nieokreślonym, zachowaniem nieokreślonym a zachowaniem zdefiniowanym przy wdrażaniu .
Musisz także o tym wiedzieć the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified
.
Na przykład:
int x = 5, y = 6;
int z = x++ + y++; //it is unspecified whether x++ or y++ will be evaluated first.
Kolejny przykład tutaj .
Teraz §5/4
mówi Standard
- 1) Między poprzednim a następnym punktem sekwencji obiekt skalarny powinien mieć zmodyfikowaną wartość zapisaną co najwyżej raz na podstawie oceny wyrażenia.
Co to znaczy?
Nieformalnie oznacza to, że między dwoma punktami sekwencji zmiennej nie wolno modyfikować więcej niż jeden raz. W wyrażeniu wyrażenie next sequence point
zwykle znajduje się na kończącym średniku, a previous sequence point
na końcu poprzedniej instrukcji. Wyrażenie może również zawierać pośrednie sequence points
.
Z powyższego zdania następujące wyrażenia wywołują Niezdefiniowane zachowanie:
i++ * ++i; // UB, i is modified more than once btw two SPs
i = ++i; // UB, same as above
++i = 2; // UB, same as above
i = ++i + 1; // UB, same as above
++++++i; // UB, parsed as (++(++(++i)))
i = (i, ++i, ++i); // UB, there's no SP between `++i` (right most) and assignment to `i` (`i` is modified more than once btw two SPs)
Ale następujące wyrażenia są w porządku:
i = (i, ++i, 1) + 1; // well defined (AFAIK)
i = (++i, i++, i); // well defined
int j = i;
j = (++i, i++, j*i); // well defined
- 2) Ponadto dostęp do poprzedniej wartości można uzyskać wyłącznie w celu ustalenia wartości, która ma być przechowywana.
Co to znaczy? Oznacza to, że jeśli obiekt jest zapisany w pełnym wyrażeniu, każdy dostęp do niego w tym samym wyrażeniu musi być bezpośrednio zaangażowany w obliczenie wartości, która ma zostać zapisana .
Na przykład w i = i + 1
całym dostępie i
(w LHS i RHS) są bezpośrednio zaangażowani w obliczanie wartości, która ma zostać zapisana. Więc w porządku.
Ta reguła skutecznie ogranicza wyrażenia prawne do tych, w których dostęp wyraźnie demonstruje modyfikację.
Przykład 1:
std::printf("%d %d", i,++i); // invokes Undefined Behaviour because of Rule no 2
Przykład 2:
a[i] = i++ // or a[++i] = i or a[i++] = ++i etc
jest niedozwolone, ponieważ jeden z dostępów i
(ten w a[i]
) nie ma nic wspólnego z wartością, która ostatecznie jest przechowywana w i (co dzieje się w in i++
), więc nie ma dobrego sposobu na zdefiniowanie - ani dla naszego zrozumienia, ani dla kompilatora - czy dostęp powinien mieć miejsce przed czy po zapisaniu przyrostowej wartości. Zachowanie jest więc niezdefiniowane.
Przykład 3:
int x = i + i++ ;// Similar to above
Odpowiedz na odpowiedź dla C ++ 11 tutaj .
*p++ = 4
nie jest niezdefiniowanym zachowaniem.*p++
jest interpretowany jako*(p++)
.p++
zwracap
(kopia), a wartość jest przechowywana pod poprzednim adresem. Dlaczego miałoby to wywoływać UB? Jest całkowicie w porządku.