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.++i
a przypisaniem doi
. Drugie wyrażenie nie wywołuje UB, ponieważ wyrażeniei
nie zmienia wartościi
. W drugim przykładziei++
po nim następuje punkt sekwencji (,
) przed wywołaniem operatora przypisania.Jest to kontynuacja mojej poprzedniej odpowiedzi i zawiera materiały związane z C ++ 11. .
Wymagania wstępne : Podstawowa znajomość relacji (matematyka).
Czy to prawda, że w C ++ 11 nie ma Punktów Sekwencji?
Tak! To jest najprawdziwsza prawda.
Sekwencja Punkty zostały zastąpione przez zsekwencjonowany Przed i Sequenced Po (a Unsequenced i Indeterminately Sequenced ) stosunki w C ++ 11.
Czym dokładnie jest ta sekwencja przed?
Sekwencją wcześniej (§ 1.9 / 13) jest relacja, która jest:
między ocenami wykonanymi przez pojedynczy wątek i indukuje ścisłą częściową kolejność 1
Formalnie oznacza to, że otrzymano dowolne dwie oceny (patrz poniżej),
A
aB
jeśliA
zostanie to zsekwencjonowane wcześniejB
, wykonanieA
poprzedza wykonanieB
. JeśliA
nie jest sekwencjonowany wcześniejB
iB
nie jest sekwencjonowany wcześniejA
, toA
i nieB
są sekwencjonowane 2 .Oceny
A
iB
są one sekwencjonowane w nieokreślony sposób, gdy alboA
jest sekwencjonowane przed,B
alboB
sekwencjonowane przedA
, ale nie jest określone, które 3 .[Uwagi]
1: Dokładna kolejność częściowo jest binarny związek
"<"
przez zestawP
, który jestasymmetric
, atransitive
, to znaczy dla wszystkicha
,b
ic
wP
mamy że:........ (I). jeśli a <b, to ¬ (b <a) (
asymmetry
);........ (ii). jeśli a <b i b <c, to a <c (
transitivity
).2: Wykonanie niesekwencjonowanych ocen może się nakładać .
3: Oceny nieokreślone sekwencyjnie nie mogą się pokrywać , ale można je wykonać najpierw.
Jakie jest znaczenie słowa „ocena” w kontekście C ++ 11?
W C ++ 11 ocena wyrażenia (lub podwyrażenia) ogólnie obejmuje:
obliczenia wartości (w tym określenie tożsamości obiektu do oceny wartości glwaue i pobranie wartości wcześniej przypisanej do obiektu do oceny wartości )
inicjacja efektów ubocznych .
Teraz (§ 1.9 / 14) mówi:
Trywialny przykład:
int x;
x = 10;
++x;
Obliczenia wartości i związane z nimi skutki uboczne
++x
są sekwencjonowane po obliczeniu wartości i skutkach ubocznychx = 10;
Więc musi być jakiś związek między niezdefiniowanym zachowaniem a wyżej wymienionymi rzeczami, prawda?
Tak! Dobrze.
W (§ 1.9 / 15) wspomniano o tym
Na przykład :
+
operatora nie ma wpływu na siebie nawzajem.<<
i>>
operatorów nie ma wpływu na siebie nawzajem.4: W wyrażeniu, które jest oceniane więcej niż raz podczas wykonywania programu, niesekwencjonowane i nieokreślone sekwencyjnie oceny jego podwyrażeń nie muszą być wykonywane konsekwentnie w różnych ocenach.
Oznacza to w
x + y
obliczaniu wartościx
iy
są one sekwencjonowane przed obliczeniem wartości dla(x + y)
.Co ważniejsze
Przykłady:
i = i++ * ++i; // Undefined Behaviour
i = ++i + i++; // Undefined Behaviour
i = ++i + ++i; // Undefined Behaviour
i = v[i++]; // Undefined Behaviour
i = v[++i]: // Well-defined Behavior
i = i++ + 1; // Undefined Behaviour
i = ++i + 1; // Well-defined Behaviour
++++i; // Well-defined Behaviour
f(i = -1, i = -1); // Undefined Behaviour (see below)
Wyrażenia
(5)
,(7)
a(8)
nie powoływać niezdefiniowanej zachowanie. Bardziej szczegółowe wyjaśnienia znajdziesz w poniższych odpowiedziach.Uwaga końcowa :
Jeśli znajdziesz jakiś błąd w poście, zostaw komentarz. Zaawansowani użytkownicy (z rep> 20000) nie wahaj się edytować wpisu w celu poprawienia literówek i innych błędów.
źródło
f(i = -1, i = 1)
?++i
(będąc ocenianą wartość przed+
operatorem, który jej używa) standard wciąż nie mówi, że jego efekt uboczny musi zostać zakończony. Ale w rzeczywistości, ponieważ zwraca ref Dolvalue
który jesti
sam, musi mieć gotowego efektu bocznego ponieważ ocena musi być zakończona, zatem wartość ta musi być na bieżąco. To była szalona część.C ++ 17 (
N4659
) zawiera propozycję Udoskonalenia kolejności oceny wyrażeń dla Idiomatic C ++, która określa bardziej rygorystyczną kolejność oceny wyrażeń.W szczególności następujące zdanie
wraz z następującym wyjaśnieniem
unieważnić kilka przypadków wcześniej nieokreślonego zachowania, w tym ten, o którym mowa:
Jednak kilka innych podobnych przypadków nadal prowadzi do nieokreślonego zachowania.
W
N4140
:Ale w
N4659
Oczywiście użycie kompilatora zgodnego z C ++ 17 niekoniecznie oznacza, że należy zacząć pisać takie wyrażenia.
źródło
i = i++ + 1;
jest zdefiniowane zachowanie w c ++ 17, myślę, że nawet jeśli „prawy operand jest sekwencjonowany przed lewym operandem”, jednak modyfikacja „i ++” i efekt uboczny przypisania nie są konsekwentne, proszę podać więcej szczegółów, aby je zinterpretowaćDomyślam się, że istnieje podstawowa przyczyna zmiany, ale nie tylko kosmetyczne jest wyjaśnienie starej interpretacji: przyczyną jest współbieżność. Nieokreślona kolejność opracowywania polega jedynie na wybraniu jednego z kilku możliwych szeregowań szeregowych, jest to zupełnie inna sytuacja niż przed i po zamówieniach, ponieważ jeśli nie ma określonego zamówienia, możliwa jest równoczesna ocena: nie w przypadku starych reguł. Na przykład w:
poprzednio albo a następnie b, albo b następnie a. Teraz, aib można ocenić za pomocą instrukcji przeplecionych lub nawet na różnych rdzeniach.
źródło
W
C99(ISO/IEC 9899:TC3)
dotychczasowej dyskusji, która wydaje się być nieobecna, poczyniono następujące rozważania dotyczące kolejności oceny.źródło