Dlaczego` i = ++i + 1 ' jest nieokreślone?

Rozważmy następującą normę C++ ISO/IEC 14882: 2003 (E) citation (sekcja 5, ust. 4):

Z wyjątkiem sytuacji, gdy zaznaczono, kolejność ocena funkcjonowania poszczególnych operatory i podekspresje poszczególnych wyrażenia, a kolejność w jakie skutki uboczne mają miejsce, jest nieokreślone. 53) pomiędzy poprzednimi i następna Sekwencja wskazuje Skalar obiekt posiada swoją wartość zapisaną modyfikowane co najwyżej raz przez ocena wyrażenia. Ponadto, wcześniejsza wartość wynosi dostęp tylko w celu określenia wartości do przechowywania. Wymagania tego ustęp jest spełniony dla każdego dopuszczalne zamawianie podekspresje pełnego wyrażenia; w przeciwnym razie zachowanie jest nieokreślone. [Przykład:

i = v[i++];  // the behavior is unspecified 
i = 7, i++, i++;  //  i becomes 9 

i = ++i + 1;  // the behavior is unspecified 
i = i + 1;  // the value of i is incremented 

- end example]

Byłem zaskoczony, że i = ++i + 1 daje niezdefiniowaną wartość i. Czy ktoś zna implementację kompilatora, która nie daje 2 dla następującego przypadku?

int i = 0;
i = ++i + 1;
std::cout << i << std::endl;

The rzecz w tym, że operator= ma dwa args. Pierwsza to zawsze i Referencja. Kolejność oceny nie ma w tym przypadku znaczenia. Nie widzę żadnego problemu poza standardem C++.

Proszę, czy Nie rozważać takie przypadki, w których kolejność argumentów jest ważna do oceny. Na przykład {[7] } jest oczywiście niezdefiniowany. Proszę, rozważcie tylko moją sprawę. i = ++i + 1.

Dlaczego Standard C++ zakazuje takich wyrażeń?

Author: Kirill V. Lyadvinsky, 2009-12-07

15 answers

Popełniasz błąd myśląc o operator= jako funkcji dwuargumentowej , gdzie skutki uboczne argumentów muszą być całkowicie ocenione przed rozpoczęciem funkcji. W takim przypadku wyrażenie i = ++i + 1 będzie miało wiele punktów sekwencji i ++i zostanie w pełni ocenione przed rozpoczęciem przypisywania. Ale tak nie jest. Co jest oceniane W Operator wewnętrzny przypisania , nie operator zdefiniowany przez użytkownika. Jest tylko jeden punkt sekwencji w tym wyrażeniu.

wynik z ++i jest oceniany przed przypisaniem (i przed operatorem dodawania), ale efekt uboczny niekoniecznie jest stosowany od razu. Wynik ++i + 1 jest zawsze taki sam jak i + 2, więc jest to wartość, która zostanie przypisana do i jako część operatora przypisania. Wynik ++i jest zawsze i + 1, więc to jest przypisywane do i jako część operatora przyrostu. Nie ma punktu sekwencji kontroluj, która wartość powinna być przypisana jako pierwsza.

Ponieważ kod narusza zasadę, że "pomiędzy poprzednią i następną sekwencją obiekt skalarny powinien mieć swoją zapisaną wartość zmodyfikowaną co najwyżej raz przez ewaluację wyrażenia", zachowanie jest niezdefiniowane. praktycznie, Jednak jest prawdopodobne, że najpierw zostanie przypisana i + 1 lub i + 2, potem zostanie przypisana druga wartość, a w końcu program będzie działał jak zwykle - bez demonów nosowych lub eksplodujących toalety, i nie i + 3, albo.

 62
Author: Rob Kennedy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-07 15:30:01

Jest to nieokreślone zachowanie, a nie (tylko) nieokreślone zachowanie, ponieważ istnieją dwa zapisy do i bez interweniującego punktu sekwencji. Tak jest z definicji w zakresie, w jakim określa to norma.

Standard pozwala kompilatorom generować kod, który opóźnia zapis do pamięci masowej - lub z innego punktu widzenia, aby ponownie odtworzyć instrukcje implementujące efekty uboczne-w dowolny sposób, o ile jest zgodny z wymaganiami punktów sekwencji.

Problem z wyrażenie to oznacza, że implikuje dwa zapisy do i bez interweniującego punktu sekwencji:

i = i++ + 1;

Jeden zapis jest dla wartości pierwotnej i "plus jeden", a drugi jest dla tej wartości" plus jeden " ponownie. Te zapisy mogą się zdarzyć w dowolnej kolejności lub wybuchnąć całkowicie, o ile standard pozwala. Teoretycznie daje to nawet implementacjom swobodę wykonywania pakietów zapisów równolegle, bez konieczności sprawdzania jednoczesnych błędów dostępu.

 37
Author: CB Bailey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-07 21:42:05

C / C++ definiuje pojęcie o nazwie sequence points , które odnosi się do punktu w wykonaniu, w którym gwarantuje się, że wszystkie efekty poprzednich ocen zostaną już wykonane. Powiedzenie i = ++i + 1 jest niezdefiniowane, ponieważ zwiększa i i przypisuje i sobie, z których żaden nie jest zdefiniowanym punktem sekwencji. Dlatego nie jest sprecyzowane, co stanie się pierwsze.

 15
Author: Charles Salvia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-07 15:04:51

Aktualizacja dla C++11 (30.09.2011)

Stop , jest to dobrze zdefiniowane W C++11. Nie został zdefiniowany tylko w C++03, ale C++11 jest bardziej elastyczny.

int i = 0;
i = ++i + 1;

Po tej linii, i będzie 2. Powodem tej zmiany było ... ponieważ to już działa w praktyce i byłoby więcej pracy, aby to było undefined niż po prostu zostawić to zdefiniowane w zasadach C++11 (faktycznie, że to działa teraz jest bardziej wypadkiem niż celową zmianą, więc proszę nie rób tego w swoim kodzie!).

Prosto z paszczy konia

Http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#637

 10
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-30 22:38:51

Biorąc pod uwagę dwie opcje: zdefiniowaną lub nieokreśloną, którą byś wybrał?

Autorzy standardu mieli dwie możliwości: zdefiniować zachowanie lub określić je jako niezdefiniowane.

Biorąc pod uwagę wyraźnie niemądry charakter pisania takiego kodu, nie ma sensu określać dla niego wyniku. Można by zniechęcić do takiego kodu, a nie go zachęcać. To nie jest użyteczne ani konieczne do niczego.

Ponadto komitety normalizacyjne nie mają możliwości zmuszać autorów kompilatorów do robienia czegokolwiek. Gdyby wymagali określonego zachowania, prawdopodobnie wymóg ten zostałby zignorowany.

Są też powody praktyczne, ale podejrzewam, że były one podporządkowane powyższej ogólnej ocenie. Ale dla przypomnienia, wszelkie zachowania wymagane dla tego rodzaju wyrażeń i pokrewnych typów ograniczą zdolność kompilatora do generowania kodu, uwzględniania wspólnych podwyrażeń, przenoszenia obiektów między rejestrami i pamięcią itp. C był już utrudniony przez słabe ograniczenia widoczności. Języki takie jak Fortran dawno temu zdały sobie sprawę, że aliasowane parametry i globale były zabójcą optymalizacji i wierzę, że po prostu zabronili ich.

Wiem, że interesowało Cię konkretne wyrażenie, ale dokładna natura danej konstrukcji nie ma większego znaczenia. Nie będzie łatwo przewidzieć, co zrobi złożony generator kodu, a język próbuje nie wymagać tych prognoz w głupich przypadkach.
 9
Author: DigitalRoss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-07 15:31:15

Ważną częścią standardu jest:

Jego przechowywana wartość zmodyfikowana co najwyżej raz przez ewaluację wyrażenia

Zmieniasz wartość dwukrotnie, raz operatorem++, raz przyporządkowaniem

 8
Author: Trent,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-07 15:08:28

Pamiętaj, że twoja kopia standardu jest nieaktualna i zawiera znany (i naprawiony) błąd tylko w pierwszej i trzeciej linijce kodu twojego przykładu, zobacz:

C++ Standard core Language Issue spis treści, rewizja 67, #351

I

Andrew Koenig: Sequence point error: unspecified or undefined?

Temat nie jest łatwy do odczytania po prostu standard (co jest dość niejasne : (w tym przypadku).

Na przykład, czy będzie dobrze (lub not) - zdefiniowane, nieokreślone lub inaczej w ogólnym przypadku w rzeczywistości zależy nie tylko od struktury instrukcji, ale także od zawartości pamięci (mówiąc konkretnie, wartości zmiennych) w momencie wykonania, inny przykład:

++i, ++i; //ok

(++i, ++j) + (++i, ++j); //ub, see the first reference below (12.1 - 12.3)

Proszę spojrzeć na (ma wszystko jasne i precyzyjne):

JTC1 / SC22 / WG14 N926 "Sequence Point Analysis"

Również Angelika Langer ma artykuł na ten temat (choć nie tak jasny jak poprzedni):

"punkty sekwencji i Ocena ekspresji w C++"

Była też dyskusja po rosyjsku (choć z pozornie błędnymi stwierdzeniami w komentarzach i w samym poście):

"Точки следования (punkty sekwencji)"

 7
Author: mlvljr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-07 22:35:29

Poniższy kod pokazuje, jak można uzyskać zły (nieoczekiwany) wynik:

int main()
{
  int i = 0;
  __asm { // here standard conformant implementation of i = ++i + 1
    mov eax, i;
    inc eax;
    mov ecx, 1;
    add ecx, eax;
    mov i, ecx;

    mov i, eax; // delayed write
  };
  cout << i << endl;
}

W rezultacie wyświetli 1.

 4
Author: Kirill V. Lyadvinsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-08 07:30:44

Zakładając, że pytasz " dlaczego język jest tak zaprojektowany?".

Mówisz, że i = ++i + i jest "oczywiście niezdefiniowany", ale i = ++i + 1 powinieneś zostawić i z określoną wartością? Szczerze mówiąc, to nie byłoby zbyt konsekwentne. Wolę mieć albo wszystko idealnie zdefiniowane, albo wszystko konsekwentnie nieokreślone. W C++ mam to drugie. Nie jest to strasznie zły wybór per se - po pierwsze, uniemożliwia pisanie złego kodu, który wprowadza pięć lub sześć modyfikacji w tym samym "oświadczenie".

 4
Author: Daniel Daranas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-14 16:11:37

Argument przez analogię: Jeśli myślisz o operatorach jako rodzajach funkcji, to ma to sens. Jeśli masz klasę z przeciążonym operator=, twoje polecenie przypisania byłoby równoważne z czymś takim:

operator=(i, ++i+1)

(pierwszy parametr jest przekazywany pośrednio przez wskaźnik this, ale jest to tylko dla ilustracji.)

Dla zwykłego wywołania funkcji jest to oczywiście niezdefiniowane. Wartość pierwszego argumentu zależy od tego, kiedy drugi argument jest oceniany. Jednak w przypadku typów prymitywnych ujdzie ci to na sucho, ponieważ oryginalna wartość i jest po prostu nadpisana; jej wartość nie ma znaczenia. Ale jeśli wykonywałbyś jakąś inną magię w swoim własnym operator=, wtedy różnica mogłaby się ujawnić.

Mówiąc najprościej: wszystkie operatory działają jak funkcje i dlatego powinny zachowywać się zgodnie z tymi samymi pojęciami. Jeśli {[5] } jest niezdefiniowana, to i = ++i również powinna być niezdefiniowana.

 3
Author: int3,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-07 15:29:17

A może zgodzimy się, że nigdy, nigdy, nie napiszemy takiego kodu? Jeśli kompilator nie wie, co chcesz zrobić, jak możesz oczekiwać, że biedny sap, który podąża za tobą, zrozumie, co chcesz zrobić? Umieszczenie i++; na jego własnej linii spowoduje , a nie zabicie Cię.

 2
Author: Ken Lange,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-07 15:46:18

Powodem jest sposób, w jaki kompilator obsługuje odczyt i Zapis wartości. Kompilator może przechowywać wartość pośrednią w pamięci i zatwierdzać ją tylko na końcu wyrażenia. Odczytujemy wyrażenie ++i jako "increase i by one and return it", ale kompilator może zobaczyć je jako " load the value of i, add one, return it, and the commit it back to memory zanim ktoś użyje go ponownie. Kompilator jest zachęcany do unikania czytania/pisania do rzeczywistej lokalizacji pamięci tak bardzo, jak to możliwe, ponieważ spowolniłoby to program.

W konkretnym przypadku i = ++i + 1 cierpi w dużej mierze z powodu potrzeby spójnych zasad zachowania. Wiele kompilatorów zrobi 'właściwą rzecz' w takiej sytuacji, ale co, jeśli jeden z i s był rzeczywiście wskaźnikiem, wskazującym na i? Bez tej reguły kompilator musiałby być bardzo ostrożny, aby upewnić się, że wykonuje ładunki i przechowuje w odpowiedniej kolejności. Zasada ta pozwala na więcej możliwości optymalizacji.

Podobny przypadek dotyczy tzw. zasady strict-aliasing. Nie można przypisać wartości (powiedzmy, an int) poprzez wartość niepowiązanego typu (powiedzmy, a float) z kilkoma wyjątkami. To sprawia, że kompilator nie musi się martwić, że niektóre float * używane zmieni wartość int i znacznie zwiększa potencjał optymalizacji.

 1
Author: coppro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-07 15:31:21

Problem polega na tym, że standard pozwala kompilatorowi na całkowitą zmianę kolejności instrukcji podczas jej wykonywania. Nie wolno jednak zmieniać kolejności poleceń (o ile taka zmiana kolejności skutkuje zmianą zachowania programu). Dlatego wyrażenie i = ++i + 1; można oceniać na dwa sposoby:

++i; // i = 2
i = i + 1;

Lub

i = i + 1;  // i = 2
++i;

Lub

i = i + 1;  ++i; //(Running in parallel using, say, an SSE instruction) i = 1

Jest to jeszcze gorsze, gdy masz typy zdefiniowane przez użytkownika rzucane w mix, gdzie operator ++ może mieć jakikolwiek wpływ na typ autor rodzaju chce, w którym to przypadku kolejność zastosowana w ocenie ma istotne znaczenie.

 1
Author: Billy ONeal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-07 15:37:03

I = v [i++]; / / zachowanie jest nieokreślone
i = ++i + 1; / / zachowanie jest nieokreślone

Wszystkie powyższe wyrażenia wywołują nieokreślone zachowanie.

I = 7, i++, I++; / / i staje się 9

To jest w porządku.

Przeczytaj Steve Summit ' S C-FAQs.

 1
Author: Prasoon Saurav,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-09-18 05:55:52

Z ++i, muszę przypisać "1", ale z i = ++i + 1, musi być przypisana wartość "2". Ponieważ nie ma interwałowego punktu sekwencji, kompilator może założyć, że ta sama zmienna nie jest zapisywana dwa razy, więc te dwie operacje mogą być wykonywane w dowolnej kolejności. więc tak, kompilator byłby poprawny, Jeśli ostateczna wartość to 1.

 0
Author: Mister,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-07 15:56:16