Zanim zaczniesz krzyczeć niezdefiniowane zachowanie, jest to wyraźnie wymienione w N4659 (C ++ 17)
i = i++ + 1; // the value of i is incremented
Jeszcze w N3337 (C ++ 11)
i = i++ + 1; // the behavior is undefined
Co się zmieniło?
Z tego, co mogę zebrać, z [N4659 basic.exec]
O ile nie zaznaczono inaczej, oceny operandów poszczególnych operatorów i podwyrażeń poszczególnych wyrażeń nie mają konsekwencji. [...] Obliczenia wartości operandów operatora są sekwencjonowane przed obliczeniem wartości wyniku operatora. Jeśli efekt uboczny w lokalizacji pamięci nie ma wpływu na inny efekt uboczny na tę samą lokalizację pamięci lub obliczenia wartości z wykorzystaniem wartości dowolnego obiektu w tej samej lokalizacji pamięci i nie są potencjalnie zbieżne, zachowanie jest niezdefiniowane.
Gdzie wartość jest zdefiniowana w [N4659 basic.type]
W przypadku typów, które można w prosty sposób skopiować, reprezentacja wartości jest zbiorem bitów w reprezentacji obiektu, który określa wartość , która jest jednym dyskretnym elementem zestawu wartości zdefiniowanego w implementacji
O ile nie zaznaczono inaczej, oceny operandów poszczególnych operatorów i podwyrażeń poszczególnych wyrażeń nie mają konsekwencji. [...] Obliczenia wartości operandów operatora są sekwencjonowane przed obliczeniem wartości wyniku operatora. Jeśli efekt uboczny na obiekcie skalarnym nie ma wpływu na inny efekt uboczny na ten sam obiekt skalarny lub obliczenie wartości z wykorzystaniem wartości tego samego obiektu skalarnego, zachowanie jest niezdefiniowane.
Podobnie wartość jest zdefiniowana w [N3337 basic.type]
W przypadku typów, które można w prosty sposób skopiować, reprezentacja wartości jest zbiorem bitów w reprezentacji obiektu, który określa wartość , która jest jednym dyskretnym elementem zestawu wartości zdefiniowanego w implementacji.
Są identyczne, z wyjątkiem wzmianki o współbieżności, która nie ma znaczenia, oraz przy użyciu lokalizacji pamięci zamiast obiektu skalarnego , gdzie
Typy arytmetyczne, typy wyliczeń, typy wskaźników, typy wskaźników do typów elementów
std::nullptr_t
oraz wersje tych typów kwalifikowane przez CV są wspólnie nazywane typami skalarnymi.
Co nie wpływa na przykład.
Operator przypisania (=) i operatory złożonego przypisania grupują wszystkie grupy od prawej do lewej. Wszystkie wymagają modyfikowalnej wartości jako lewego operandu i zwracają wartość odnoszącą się do lewego operandu. Wynik we wszystkich przypadkach jest polem bitowym, jeśli lewy operand jest polem bitowym. We wszystkich przypadkach przypisanie jest sekwencjonowane po obliczeniu wartości prawego i lewego operandu, a przed obliczeniem wartości wyrażenia przypisania. Prawy operand jest sekwencjonowany przed lewym operandem.
Operator przypisania (=) i operatory złożonego przypisania grupują wszystkie grupy od prawej do lewej. Wszystkie wymagają modyfikowalnej wartości jako lewego operandu i zwracają wartość odnoszącą się do lewego operandu. Wynik we wszystkich przypadkach jest polem bitowym, jeśli lewy operand jest polem bitowym. We wszystkich przypadkach przypisanie jest sekwencjonowane po obliczeniu wartości prawego i lewego operandu, a przed obliczeniem wartości wyrażenia przypisania.
Jedyną różnicą jest brak ostatniego zdania w N3337.
Ostatnie zdanie nie powinno mieć jednak żadnego znaczenia, ponieważ lewy operand i
nie jest ani „innym efektem ubocznym”, ani „wykorzystaniem wartości tego samego obiektu skalarnego”, ponieważ wyrażenie id jest wartością.
źródło
i = i++ + 1;
.Odpowiedzi:
W C ++ 11 czynność „przypisania”, tj. Efekt uboczny modyfikacji LHS, jest sekwencjonowana po obliczeniu wartości prawego operandu. Należy zauważyć, że jest to stosunkowo „słaba” gwarancja: powoduje sekwencjonowanie tylko w odniesieniu do obliczania wartości RHS. Nie mówi nic o skutkach ubocznych, które mogą występować w RHS, ponieważ występowanie skutków ubocznych nie jest częścią obliczania wartości . Wymagania C ++ 11 nie ustanawiają względnego sekwencjonowania między aktem przypisania a jakimikolwiek skutkami ubocznymi RHS. To właśnie tworzy potencjał dla UB.
Jedyną nadzieją w tym przypadku są wszelkie dodatkowe gwarancje udzielone przez określonych operatorów stosowanych w RHS. Gdyby RHS użył prefiksu
++
, właściwości sekwencjonowania specyficzne dla formy prefiksu++
uratowałyby dzień w tym przykładzie. Ale postfiks++
to inna historia: nie daje takich gwarancji. W C ++ 11 skutki uboczne=
i postfiks++
kończą się w tym przykładzie bez konsekwencji w stosunku do siebie. I to jest UB.W C ++ 17 do specyfikacji operatora przypisania dodano dodatkowe zdanie:
W połączeniu z powyższym stanowi bardzo silną gwarancję. Sekwencjonuje wszystko , co dzieje się w RHS (w tym wszelkie skutki uboczne) przed wszystkim , co dzieje się w LHS. Ponieważ faktyczne przypisanie jest sekwencjonowane po LHS (i RHS), to dodatkowe sekwencjonowanie całkowicie izoluje akt przypisania od jakichkolwiek skutków ubocznych obecnych w RHS. To silniejsze sekwencjonowanie eliminuje powyższe UB.
(Zaktualizowano, aby uwzględnić komentarze @John Bollinger.)
źródło
x -= y;
na przetwarzanie czegoś takiego jakmov eax,[y] / sub [x],eax
zamiastmov eax,[x] / neg eax / add eax,[y] / mov [x],eax
. Nie widzę w tym nic okresowego. Gdyby trzeba było określić uporządkowanie, najbardziej wydajnym uporządkowaniem byłoby prawdopodobnie wykonanie wszystkich obliczeń niezbędnych do identyfikacji obiektu po lewej stronie, a następnie oceny prawego operandu, a następnie wartości lewego obiektu, ale to wymagałoby posiadania terminu za czynność rozwiązania idiotyki lewego obiektu.x
iy
byłyvolatile
, które miałyby skutki uboczne. Co więcej, te same rozważania miałyby zastosowanie dox += f();
, w przypadkuf()
modyfikacjix
.Zidentyfikowałeś nowe zdanie
i poprawnie zidentyfikowałeś, że ocena lewego operandu jako wartości jest nieistotna. Jednak sekwencjonowanie wcześniej określa się jako relację przechodnią. Zatem kompletny prawy operand (łącznie z przyrostem) jest sekwencjonowany również przed przypisaniem. W C ++ 11 tylko obliczenie wartości prawego operandu zostało zsekwencjonowane przed przypisaniem.
źródło
W starszych standardach C ++ i C11 definicja tekstu operatora przypisania kończy się na tekście:
Oznacza to, że skutki uboczne w operandach nie są konsekwentne, a zatem zdecydowanie niezdefiniowane zachowanie, jeśli używają tej samej zmiennej.
Ten tekst został po prostu usunięty w C ++ 11, pozostawiając go nieco niejednoznaczną. Czy to UB czy nie? Zostało to wyjaśnione w C ++ 17, gdzie dodali:
Na marginesie, nawet w starszych standardach wszystko to było bardzo jasne, przykład z C99:
Zasadniczo w C11 / C ++ 11 zawiedli, kiedy usunęli ten tekst.
źródło
To są dalsze informacje do innych odpowiedzi i zamieszczam je, ponieważ często pytany jest również poniższy kod .
Wyjaśnienie w pozostałych odpowiedziach jest poprawne i dotyczy również następującego kodu, który jest teraz dobrze zdefiniowany (i nie zmienia przechowywanej wartości
i
):+ 1
Jest czerwony śledź i to naprawdę nie jest jasne, dlaczego standardowa używali go w swoich przykładach, chociaż ja pamietam ludzi argumentując na listach dyskusyjnych przed c ++ 11, że może+ 1
to różnicy z powodu zmuszania wcześnie lwartości konwersję na prawy- strona dłoni. Z pewnością nic z tego nie ma zastosowania w C ++ 17 (i prawdopodobnie nigdy nie miało zastosowania w żadnej wersji C ++).źródło