Dlaczego x = x ++ jest niezdefiniowany?

19

Jest niezdefiniowany, ponieważ modyfikuje xdwukrotnie między punktami sekwencji. Standard mówi, że jest niezdefiniowany, dlatego jest niezdefiniowany.
Tyle wiem.

Ale dlaczego?

Rozumiem, że zabranianie tego pozwala kompilatorom na lepszą optymalizację. Mogło to mieć sens, gdy wynaleziono C, ale teraz wydaje się słabym argumentem.
Gdybyśmy dzisiaj wymyślili na nowo C, czy zrobilibyśmy to w ten sposób, czy można to zrobić lepiej?
A może jest głębszy problem, który utrudnia zdefiniowanie spójnych reguł dla takich wyrażeń, więc najlepiej ich zabronić?

Przypuśćmy, że dzisiaj wymyślimy C na nowo. Chciałbym zasugerować proste reguły dla wyrażeń takich jak x=x++, które wydają mi się działać lepiej niż istniejące reguły.
Chciałbym poznać Twoją opinię na temat sugerowanych reguł w porównaniu do istniejących lub innych sugestii.

Sugerowane zasady:

  1. Między punktami sekwencji kolejność oceny jest nieokreślona.
  2. Efekty uboczne występują natychmiast.

Nie wiąże się to z niezdefiniowanym zachowaniem. Wyrażenia oceniają tę lub inną wartość, ale na pewno nie sformatują dysku twardego (o dziwo, nigdy nie widziałem implementacji, w której x=x++formatuje dysk twardy).

Przykładowe wyrażenia

  1. x=x++- Dobrze zdefiniowany, nie zmienia się x.
    Najpierw xjest zwiększany (natychmiast po x++ocenie), a następnie zapisywana jest jego stara wartość x.

  2. x++ + ++x- Przyrosty xdwukrotnie, ocenia na 2*x+2.
    Chociaż każda ze stron może być oceniona jako pierwsza, wynikiem jest albo x + (x+2)(lewa strona pierwsza), albo (x+1) + (x+1)(prawa strona pierwsza).

  3. x = x + (x=3)- Nieokreślony, xustaw na jeden x+3lub 6.
    Jeśli prawa strona jest oceniana jako pierwsza, to jest x+3. Możliwe jest również, że x=3najpierw jest oceniane, więc tak jest 3+3. W obu przypadkach x=3przypisanie następuje natychmiast po x=3dokonaniu oceny, więc zapisana wartość jest zastępowana przez inne przypisanie.

  4. x+=(x=3)- Dobrze zdefiniowane, ustawione xna 6.
    Można argumentować, że jest to tylko skrót dla powyższego wyrażenia.
    Ale powiedziałbym, że +=należy to wykonać po x=3, a nie w dwóch częściach (przeczytaj x, oceń x=3, dodaj i zapisz nową wartość).

Jaka jest zaleta?

Niektóre komentarze podniosły ten dobry punkt.
Z pewnością nie sądzę, aby wyrażenia takie jak x=x++powinny być używane w normalnym kodzie.
Faktycznie, jestem o wiele bardziej rygorystyczne niż - Myślę, że tylko dobre wykorzystanie dla x++jako x++;samotnie.

Myślę jednak, że reguły językowe muszą być tak proste, jak to możliwe. W przeciwnym razie programiści po prostu ich nie rozumieją. reguła zabraniająca dwukrotnej zmiany zmiennej między punktami sekwencji jest z pewnością regułą, której większość programistów nie rozumie.

Bardzo podstawowa zasada jest następująca:
jeśli A jest ważne, a B jest ważne i są one połączone w prawidłowy sposób, wynik jest ważny.
xjest prawidłową wartością L, x++jest prawidłowym wyrażeniem i =jest prawidłowym sposobem łączenia wartości L i wyrażenia, więc dlaczego x=x++nie jest legalne?
Standard C stanowi tutaj wyjątek, a ten wyjątek komplikuje reguły. Możesz przeszukać stackoverflow.com i zobaczyć, jak bardzo ten wyjątek wprowadza ludzi w błąd.
Więc mówię - pozbyć się tego zamieszania.

=== Podsumowanie odpowiedzi ===

  1. Dlaczego to robisz
    Próbowałem wyjaśnić w powyższej sekcji - chcę, aby reguły C były proste.

  2. Potencjał do optymalizacji:
    Odsuwa to trochę kompilatora, ale nie widziałem niczego, co przekonałoby mnie, że może być znaczące.
    Nadal można przeprowadzić większość optymalizacji. Na przykład a=3;b=5;można zmienić kolejność, nawet jeśli standard określa kolejność. Wyrażenia takie jak a=b[i++]wciąż można zoptymalizować podobnie.

  3. Nie możesz zmienić istniejącego standardu.
    Przyznaję, że nie mogę. Nigdy nie myślałem, że mogę iść naprzód i zmieniać standardy i kompilatory. Chciałem tylko pomyśleć, czy można było zrobić inaczej.

ugoren
źródło
10
Dlaczego to jest dla ciebie ważne? Czy należy to zdefiniować, a jeśli tak, to dlaczego? Przypisywanie się xdo samego siebie nie ma sensu , a jeśli chcesz zwiększyć x, możesz po prostu powiedzieć x++;- nie trzeba tego przypisania. Powiedziałbym, że nie należy tego definiować tylko dlatego, że trudno byłoby sobie przypomnieć, co się stanie.
Kaleb
4
Moim zdaniem jest to dobre pytanie („Niektórzy ludzie widzą rzeczy takimi, jakimi są i pytają dlaczego, śnię o rzeczach, które nigdy nie istniały i pytam dlaczego nie”). To (moim zdaniem) pytanie dotyczy wyłącznie projektowania języka, używając na przykład składni C, a nie pytania o składnię C. Osobiście uważam, że powodem, dla którego nie zdefiniowaliśmy zachowania dla wyrażeń takich jak x ++ + ++ x lub x = x ++, jest po prostu dlatego, że istnieje możliwość ich błędnego odczytania.
Jamie Taylor,
5
@ugoren: Dlaczego musisz przewidzieć wynik. Nikt przy zdrowych zmysłach nie napisałby takiego kodu (jak już wspomniano kilka razy), nawet jeśli napisałeś taki kod, zostałby odrzucony przy pierwszej recenzji kodu. Nie ma więc potrzeby definiowania zachowania i dawania optymalizatorowi najlepszej szansy na jego optymalizację. W każdym zaproponowanym przez ciebie przykładzie zastrzeliłbym kogoś, gdyby dodał go do bazy kodu.
Martin York,
3
Znalazłbym bardziej interesujące pytanie, dlaczego nie ma błędu, aby to napisać? Z pewnością kompilator może wykryć swoje nieokreślone zachowanie i dlatego nie może być tym, czego naprawdę chciał użytkownik, więc dlaczego nie popełnił błędu? Rozumiem, że niektóre przypadki nieokreślonego zachowania są trudne do wykrycia, ale to nie jest;
JohnB
3
reguła zabraniająca dwukrotnej zmiany zmiennej między punktami sekwencji jest z pewnością zasadą, której większość programistów nie rozumie. ” - Czy masz na to jakieś dowody? Większość pytań, które widziałem, zadawano, ponieważ programiści nie wiedzieli o regule. Czy istnieją dowody na to, że większość z nich nadal tego nie rozumiała po wyjaśnieniu?
Zabezpiecz

Odpowiedzi:

24

Może powinieneś najpierw odpowiedzieć na pytanie, dlaczego należy to zdefiniować? Czy jest jakaś przewaga w stylu programowania, czytelności, łatwości konserwacji lub wydajności, umożliwiając takie wyrażenia z dodatkowymi efektami ubocznymi? Jest

y = x++ + ++x;

bardziej czytelny niż

y = 2*x + 2;
x += 2;

Biorąc pod uwagę, że taka zmiana jest niezwykle fundamentalna i stanowi przełamanie istniejącej bazy kodu.

Bezpieczne
źródło
1
Dodałem sekcję „dlaczego” do mojego pytania. Z pewnością nie sugeruję używania tych wyrażeń, ale interesują mnie proste reguły określające znaczenie wyrażenia.
ugoren
Ponadto ta zmiana nie psuje istniejącego kodu, chyba że wywołała niezdefiniowane zachowanie. Popraw mnie, jeśli się mylę.
ugoren,
3
Cóż, bardziej filozoficzna odpowiedź: obecnie jest nieokreślona. Jeśli żaden programista go nie używa, nie musisz rozumieć takich wyrażeń, ponieważ nie powinno być żadnego kodu. Jeśli musisz je zrozumieć, to oczywiście musi być dużo kodu, który opiera się na nieokreślonym zachowaniu. ;)
Zabezpiecz
1
Z definicji nie jest to łamanie żadnej istniejącej bazy kodu w celu zdefiniowania zachowań. Jeśli zawierały UB, z definicji były już zepsute.
DeadMG
1
@ugoren: Twoja sekcja „dlaczego” wciąż nie odpowiada na praktyczne pytanie: dlaczego chcesz mieć to dziwaczne wyrażenie w kodzie? Jeśli nie potrafisz znaleźć przekonującej odpowiedzi, cała dyskusja jest dyskusyjna.
Mike Baranczak,
20

Argument, że takie niezdefiniowane zachowanie umożliwia lepszą optymalizację, nie jest dziś słaby. W rzeczywistości jest dziś znacznie silniejszy niż wtedy, gdy C był nowy.

Gdy C było nowe, maszyny, które mogłyby to wykorzystać do lepszej optymalizacji, były głównie modelami teoretycznymi. Ludzie mówili o możliwości budowania procesorów, w których kompilator instruuje procesor o tym, jakie instrukcje mogą / powinny być wykonywane równolegle z innymi instrukcjami. Wskazywali oni na fakt, że dopuszczenie tego do nieokreślonego zachowania oznaczało, że na takim procesorze, jeśli kiedykolwiek istniałby naprawdę, można było zaplanować wykonanie części „inkrementacji” równolegle z resztą strumienia instrukcji. Chociaż mieli rację co do teorii, w tamtym czasie niewiele było sprzętu, który mógłby naprawdę skorzystać z tej możliwości.

To już nie tylko teoria. Teraz istnieje sprzęt w produkcji i szeroko stosowany (np. Itanium, DSL VLIW), który naprawdę może z tego skorzystać. Oni naprawdę zrobić pozwolić kompilator do generowania strumienia instrukcji, która określa, że instrukcje X, Y i Z mogą być wykonywane równolegle. To nie jest już model teoretyczny - to prawdziwy sprzęt w prawdziwym użyciu, wykonujący prawdziwą pracę.

IMO sprawia, że ​​zdefiniowane zachowanie jest bliskie najgorszemu „rozwiązaniu” problemu. Oczywiście nie powinieneś używać takich wyrażeń. W przypadku znacznej większości kodu idealnym rozwiązaniem byłoby po prostu całkowite odrzucenie takich wyrażeń przez kompilator. W tym czasie kompilatory C nie przeprowadzały analizy przepływu koniecznej do niezawodnego wykrycia tego. Nawet w czasach oryginalnego standardu C wciąż nie było to wcale powszechne.

Nie jestem też pewien, czy byłoby to dzisiaj akceptowalne dla społeczności - podczas gdy wiele kompilatorów może przeprowadzić tego rodzaju analizę przepływu, zazwyczaj robią to tylko wtedy, gdy żądasz optymalizacji. Wątpię, aby większość programistów chciała spowolnić kompilacje „debugowania” tylko po to, aby móc odrzucić kod, którego (normalnie) nigdy nie napisaliby.

To, co zrobił C, jest pół-rozsądnym drugim najlepszym wyborem: powiedz ludziom, aby tego nie robili, pozwalając (ale nie wymagając) kompilatorowi na odrzucenie kodu. Pozwala to uniknąć (jeszcze bardziej) spowolnienia kompilacji dla osób, które nigdy go nie użyłyby, ale nadal pozwala komuś napisać kompilator, który odrzuci taki kod, jeśli zechce (i / lub będzie miał flagi, które odrzuci go, z których ludzie mogą korzystać lub nie według własnego uznania).

Przynajmniej IMO sprawi, że to zdefiniowane zachowanie byłoby (przynajmniej blisko) najgorszą możliwą decyzją do podjęcia. Na sprzęcie w stylu VLIW wybrałbyś generowanie wolniejszego kodu dla racjonalnego wykorzystania operatorów inkrementacji, tylko ze względu na kiepski kod, który ich nadużywa, lub zawsze wymaga obszernej analizy przepływu, aby udowodnić, że nie masz do czynienia z kiepski kod, dzięki czemu możesz tworzyć wolny (serializowany) kod tylko wtedy, gdy jest to naprawdę konieczne.

Konkluzja: jeśli chcesz wyleczyć ten problem, powinieneś myśleć w przeciwnym kierunku. Zamiast definiować, co robi taki kod, powinieneś zdefiniować język, aby takie wyrażenia po prostu w ogóle nie były dozwolone (i żyć z faktem, że większość programistów prawdopodobnie zdecyduje się na szybszą kompilację zamiast egzekwowania tego wymagania).

Jerry Coffin
źródło
IMO nie ma powodów, by sądzić, że w większości przypadków wolniejsze instrukcje są tak naprawdę znacznie wolniejsze niż szybkie instrukcje i że zawsze będą miały wpływ na wydajność programu. Klasyfikowałbym ten przedwczesną optymalizację.
DeadMG
Może coś mi brakuje - jeśli nikt nigdy nie powinien pisać takiego kodu, po co dbać o jego optymalizację?
ugoren
1
@ugoren: pisanie kodu takiego jak a=b[i++];(na przykład) jest w porządku, a optymalizacja to dobra rzecz. Nie widzę jednak sensu ranienia takiego rozsądnego kodu, więc coś takiego ++i++ma określone znaczenie.
Jerry Coffin
2
@ugoren Problem dotyczy diagnozy. Jedynym celem nie wprost wykluczenia wyrażeń, takich jak, ++i++jest właśnie to, że generalnie trudno jest odróżnić je od prawidłowych wyrażeń o skutkach ubocznych (takich jak a=b[i++]). To może wydawać się dla nas dość proste, ale jeśli dobrze pamiętam Dragon Book, to w rzeczywistości jest to trudny problem NP. To dlaczego takie zachowanie jest UB, zamiast zabronione.
Konrad Rudolph
1
Nie wierzę, że wydajność jest ważnym argumentem. Trudno mi uwierzyć, że sprawa jest dość powszechna, biorąc pod uwagę bardzo niewielką różnicę i bardzo szybkie wykonanie w obu przypadkach, aby zauważalny był niewielki spadek wydajności - nie wspominając o tym, że w wielu procesorach i architekturach definiowanie go jest faktycznie bezpłatne.
DeadMG,
9

Eric Lippert, główny projektant w zespole kompilatorów C #, opublikował na swoim blogu artykuł na temat wielu rozważań, które należy podjąć, aby uczynić funkcję niezdefiniowaną na poziomie specyfikacji języka. Oczywiście C # jest innym językiem, z różnymi czynnikami wpływającymi na jego projekt językowy, ale jego uwagi są istotne.

W szczególności zwraca uwagę na kwestię posiadania kompilatorów dla języka, który ma istniejące implementacje, a także przedstawicieli w komitecie. Nie jestem pewien, czy tak jest w tym przypadku, ale zwykle dotyczy większości dyskusji na temat specyfikacji C i C ++.

Warto również zauważyć, jak powiedziałeś, potencjał wydajności optymalizacji kompilatora. Chociaż prawdą jest, że wydajność procesorów w tych dniach jest o wiele rzędów wielkości większa niż w czasach, gdy C był młody, duża część programowania w C wykonywana w tych dniach jest wykonywana specjalnie ze względu na potencjalny wzrost wydajności i potencjalną (hipotetyczną przyszłość ) Optymalizacje instrukcji procesora i optymalizacje przetwarzania wielordzeniowego byłyby głupie, aby wykluczyć ze względu na zbyt restrykcyjny zestaw zasad postępowania z efektami ubocznymi i punktami sekwencji.

Tanzelax
źródło
Z artykułu, do którego linkujesz, wydaje się, że C # nie jest daleki od tego, co sugeruję. Kolejność działań niepożądanych definiuje się „obserwując z wątku, który powoduje działania niepożądane”. Nie wspominałem o wielowątkowości, ale ogólnie C nie gwarantuje wiele obserwatorowi w innym wątku.
ugoren
5

Najpierw spójrzmy na definicję niezdefiniowanego zachowania:

3.4.3

1 unde fi ned zachowanie
zachowanie, po zastosowaniu nieprzenośnej lub bledne programu konstruktu lub błędnych danych, na którym niniejszy standard międzynarodowa nakłada żadnych wymagań

2 UWAGA Możliwe unde zdefiniowane zakresy zachowania od ignorując sytuację całkowicie nieprzewidywalne wyniki, aby zachowywać się podczas tłumaczenia lub programu wykonania w udokumentowany sposób charakterystyczny dla środowiska (z wydaniem komunikatu diagnostycznego lub bez niego), w celu zakończenia tłumaczenia lub wykonania (z wydaniem komunikatu diagnostycznego).

3 PRZYKŁAD Przykładem nieokreślonego zachowania jest zachowanie przy przepełnieniu liczby całkowitej

Innymi słowy, „niezdefiniowane zachowanie” oznacza po prostu, że kompilator może dowolnie obsługiwać sytuację w dowolny sposób, a każde takie działanie jest uważane za „prawidłowe”.

Podstawą omawianego problemu jest następująca klauzula:

6.5 Wyrażenia

...
3 Grupowanie operatorów i operandów jest wskazywane przez składnię. 74) z wyjątkiem przypadków specy fi ed później (dla funkcji-call (), &&, ||, ?:, i operatorzy przecinek), kolejności oceny podwyrażeń i kolejność, w której odbywają się skutki uboczne są zarówno unspeci fi ed .

Podkreślenie dodane.

Biorąc pod uwagę wyrażenie jak

x = a++ * --b / (c + ++d);

Podwyrażenia a++, --b, c, i ++dmoże być oceniana w dowolnej kolejności . Ponadto skutki uboczne a++, --bi ++dmogą być stosowane w dowolnym momencie przed następnym punkcie sekwencji (IOW, nawet jeśli a++jest oceniane przed --b, to nie gwarantuje, że azostanie zaktualizowany przed --bjest analizowany). Jak inni powiedzieli, uzasadnieniem tego zachowania jest zapewnienie implementacji swobody w celu optymalnego uporządkowania operacji.

Z tego powodu jednak wyrażenia takie jak

x = x++
y = i++ * i++
a[i] = i++
*p++ = -*p    // this one bit me just yesterday

itp., przyniesie różne wyniki dla różnych implementacji (lub dla tej samej implementacji z różnymi ustawieniami optymalizacji lub w oparciu o otaczający kod itp.).

Zachowanie pozostaje niezdefiniowane, więc kompilator nie jest zobowiązany do „robienia właściwych rzeczy”, cokolwiek by to nie było. Powyższe przypadki są wystarczająco łatwe do złapania, ale istnieje nietrywialna liczba przypadków, które byłyby trudne do niemożliwości do złapania w czasie kompilacji.

Oczywiście można zaprojektować taki język, aby kolejność oceny i kolejność stosowania efektów ubocznych były ściśle określone, a zarówno Java, jak i C # robią to, w dużej mierze, aby uniknąć problemów, do których prowadzą definicje C i C ++.

Dlaczego więc nie wprowadzono tej zmiany do C po 3 standardowych wersjach? Po pierwsze, istnieje 40 lat starszego kodu C i nie ma gwarancji, że taka zmiana nie złamie tego kodu. Nakłada to nieco obciążenia na autorów kompilatorów, ponieważ taka zmiana natychmiast sprawiłaby, że wszystkie istniejące kompilatory nie byłyby zgodne; wszyscy musieliby dokonać znaczących przeróbek. Nawet na szybkich, nowoczesnych procesorach nadal można osiągnąć rzeczywisty wzrost wydajności poprzez zmianę kolejności oceny.

John Bode
źródło
1
Bardzo dobre wyjaśnienie problemu. Nie zgadzam się z łamaniem starszych aplikacji - sposób, w jaki wdrażane jest niezdefiniowane / nieokreślone zachowanie, czasami zmienia się między wersjami kompilatora, bez żadnych zmian w standardzie. Nie sugeruję zmiany żadnego określonego zachowania.
ugoren
4

Najpierw musisz zrozumieć, że nie tylko x = x ++ jest niezdefiniowane. Nikt nie dba o x = x ++, ponieważ bez względu na to, co byś zdefiniował, nie ma sensu. Nieokreślone jest bardziej jak „a = b ++ gdzie aib są takie same” - tj

void f(int *a, int *b) {
    *a = (*b)++;
}
int i;
f(&i, &i);

Istnieje kilka różnych sposobów implementacji tej funkcji, w zależności od tego, co jest najbardziej wydajne dla architektury procesora (i dla otaczających instrukcji, w przypadku gdy jest to funkcja bardziej złożona niż w przykładzie). Na przykład dwa oczywiste:

load r1 = *b
copy r2 = r1
increment r1
store *b = r1
store *a = r2

lub

load r1 = *b
store *a = r1
increment r1
store *b = r1

Zauważ, że pierwszy wymieniony powyżej, ten, który wykorzystuje więcej instrukcji i więcej rejestrów, jest tym, którego należy użyć we wszystkich przypadkach, w których nie można udowodnić, że aib są różne.

Losowo 832
źródło
Rzeczywiście pokazujesz przypadek, w którym moja sugestia skutkuje większą liczbą operacji na maszynie, ale dla mnie wygląda to nieistotnie. Kompilator wciąż ma pewną swobodę - jedynym prawdziwym wymaganiem, które dodałem, jest przechowywanie go bwcześniej a.
ugoren
3

Dziedzictwo

Założenie, że C można dziś wymyślić na nowo, nie może zostać przyjęte. Jest tak wiele linii kodów C, które zostały wyprodukowane i są codziennie używane, że zmiana zasad gry w środku gry jest po prostu niewłaściwa.

Oczywiście możesz wymyślić nowy język, powiedzmy C + = , według własnych zasad. Ale to nie będzie C.

mouviciel
źródło
2
Nie sądzę, żebyśmy mogli dziś wymyślić C na nowo. Nie oznacza to, że nie możemy omawiać tych problemów. Jednak to, co sugeruję, nie jest tak naprawdę na nowo. Konwersję niezdefiniowanego zachowania na zdefiniowane lub nieokreślone można wykonać podczas aktualizacji standardu, a językiem nadal będzie C.
ugoren
2

Zadeklarowanie, że coś jest zdefiniowane, nie zmieni istniejących kompilatorów, aby były zgodne z twoją definicją. Jest to szczególnie prawdziwe w przypadku założenia, na którym można było polegać w sposób jawny lub dorozumiany w wielu miejscach.

Główny problem związany z założeniem nie dotyczy x = x++;(kompilatory mogą łatwo to sprawdzić i powinny ostrzec), ale ma *p1 = (*p2)++i jest równoważny ( p1[i] = p2[j]++;gdy p1 i p2 są parametrami funkcji), gdzie kompilator nie może łatwo stwierdzić, czy p1 == p2(w C99 restrictdodano w celu rozłożenia możliwości przyjęcia p1! = p2 między punktami sekwencji, dlatego uznano, że możliwości optymalizacji były ważne).

AProgrammer
źródło
Nie rozumiem, jak moja sugestia zmienia cokolwiek w odniesieniu do p1[i]=p2[j]++. Jeśli kompilator nie zakłada aliasingu, nie ma problemu. Jeśli nie, musi przejść obok książki - p2[j]najpierw zwiększ , a p1[i]później przechowaj . Z wyjątkiem utraconych możliwości optymalizacji, które nie wydają się znaczące, nie widzę problemu.
ugoren
Drugi akapit nie był niezależny od pierwszego, ale jest przykładem rodzaju miejsc, w których założenie może się wkraść i będzie trudne do wyśledzenia.
AProgrammer
Pierwszy akapit mówi coś całkiem oczywistego - kompilatory będą musiały zostać zmienione, aby były zgodne z nowym standardem. Nie sądzę, żebym miał szansę to ustandaryzować i zachęcić autorów kompilatora do naśladowania. Po prostu myślę, że warto to omówić.
ugoren
Problemem nie jest to, że trzeba zmienić kompilatory o każdej zmianie języka, która tego potrzebuje, chodzi o to, że zmiany są wszechobecne i trudno je znaleźć. Najbardziej praktyczne podejście będzie prawdopodobnie zmiany formatu pośredniego, na którym pracuje Optimizer, czyli udawanie, że x = x++;nie zostało napisane, ale t = x; x++; x = t;albo x=x; x++;albo cokolwiek chcesz jako semantyczny (ale co z diagnostyką?). W przypadku nowego języka po prostu porzuć działania niepożądane.
AProgrammer
Nie wiem za dużo o strukturze kompilatora. Gdybym naprawdę chciał zmienić wszystkie kompilatory, bardziej by mnie to obchodziło. Ale może traktując to x++jako punkt sekwencyjny, jakby to było wywołanie funkcji, załatwi sprawę inc_and_return_old(&x).
ugoren
-1

W niektórych przypadkach ten rodzaj kodu został zdefiniowany w nowym standardzie C ++ 11.

DeadMG
źródło
5
Możesz rozwinąć temat?
ugoren
Myślę, że x = ++xjest teraz dobrze zdefiniowany (ale nie x = x++)
MM