Jaki jest wynik + = w C i C ++?

93

Mam następujący kod:

#include <stdio.h>
int main(int argc, char **argv) {
    int i = 0;
    (i+=10)+=10;
    printf("i = %d\n", i);
    return 0;
}

Jeśli spróbuję skompilować go jako źródło C przy użyciu gcc, pojawia się błąd:

error: lvalue required as left operand of assignment

Ale jeśli skompiluję go jako źródło C ++ przy użyciu g ++, nie otrzymuję błędu, a kiedy uruchomię plik wykonywalny:

i = 20

Skąd to inne zachowanie?

Svetlin Mladenov
źródło
85
Inny język, inne reguły składni? Osobiście odrzuciłbym ten kod podczas przeglądu kodu.
Maksymalnie
7
Unikaj takiego kodu imo ... Niejasny dla wszystkich.
allaire
1
Niewątpliwie kod nie jest czysty i należy go unikać podczas „prawdziwego” programowania. Niemniej jednak obserwuję to samo zachowanie i chciałbym poznać jego przyczyny.
ulidtko
9
NIE jest to fragment kodu z prawdziwego oprogramowania. To tylko dziwactwo, na które natknąłem się przypadkowo.
Svetlin Mladenov
3
@JohnDibling Myślę, że upvote to konkretnie (i + = 10) + = 10. Nie wiem, w jakim języku jest legalny kod, a fakt, że mówi, że C ++ faktycznie kompiluje, intryguje mnie.
Tony318

Odpowiedzi:

133

Semantyka operatorów przypisania złożonego jest inna w C i C ++:

Standard C99, 6.5.16, część 3:

Operator przypisania przechowuje wartość w obiekcie wyznaczonym przez lewy operand. Wyrażenie przypisania ma wartość lewego operandu po przypisaniu, ale nie jest lwartością.

W C ++ 5.17.1:

Operator przypisania (=) i złożone operatory przypisania wszystkie grupują od prawej do lewej. Wszystkie wymagają modyfikowalnej lwartości jako lewego operandu i zwracają lwartość z typem i wartością lewego operandu po dokonaniu przypisania.

EDYCJA: zachowanie (i+=10)+=10w C ++ jest niezdefiniowane w C ++ 98, ale dobrze zdefiniowane w C ++ 11. Zobacz tę odpowiedź na pytanie NPE dla odpowiednich części norm.

Sergey Kalinichenko
źródło
Poprawny. Jeden zwraca wartość wyniku, a drugi zwraca zmienną (adres)
texasbruce
7
Ważne : Zauważ, że (i+=10)+=10jest to niezdefiniowane zachowanie w C ++, zobacz odpowiedź @aix.
David Rodríguez - dribeas
@ DavidRodríguez-dribeas Miałeś na myśli nieokreślony , a nie nieokreślony , prawda?
Sergey Kalinichenko
4
@dasblinkenlight: Nie, miał na myśli niezdefiniowane . W C ++ 03 i wcześniejszych modyfikacja wyniku l-wartości wyrażenia zachowuje się nieprzewidywalnie we wszystkich kompilatorach z powodu braku interweniującego punktu sekwencji. Gdyby był nieokreślony , zachowywałby się przewidywalnie, ale inaczej na różnych kompilatorach .
Justin ᚅᚔᚈᚄᚒᚔ
2
Byłoby to przydatne w takim ustawieniu int f(int &y); f(x += 10);- przekazanie odniesienia do zmodyfikowanej zmiennej do funkcji.
Phil Miller
51

Oprócz tego, że jest nieprawidłowym kodem C, wiersz

(i+=10)+=10;

spowodowałoby niezdefiniowane zachowanie zarówno w C, jak i C ++ 03, ponieważ zmieniłoby się idwukrotnie między punktami sekwencji.

Dlaczego można kompilować w C ++:

[C ++ N3242 5.17.1] Operator przypisania (=) i złożone operatory przypisania wszystkie grupują od prawej do lewej. Wszystkie wymagają modyfikowalnej lwartości jako lewego operandu i zwracają lwartość odnoszącą się do lewego operandu.

Ten sam akapit mówi o tym

We wszystkich przypadkach przypisanie jest sekwencjonowane po obliczeniu wartości prawego i lewego operandu, a przed obliczeniem wartości wyrażenia przypisania.

Sugeruje to, że w C ++ 11 wyrażenie nie ma już niezdefiniowanego zachowania.

NPE
źródło
3
Cóż, z pewnością jest to UB ze względu na punkty sekwencji. Jest to również nieprawidłowy kod w C (ale nie w C ++), ale nie ma to związku z punktami sekwencji i powinien zostać przechwycony przez kompilator.
Konrad Rudolph
2
@KonradRudolph: nie, kompilator nie jest zobowiązany do wychwytywania niezdefiniowanego zachowania, w przeciwieństwie do źle sformułowanego kodu. Nie zgadzamy się z częścią „powinien zostać przechwycony przez kompilator”.
2
Punkty sekwencji nie istnieją w C ++ 11, więc rzeczywistą przyczyną UB jest to, że istnieją dwie modyfikacje, iktóre nie są sekwencjonowane.
Mankarse
4
To nieprawda, że ​​jest to niezdefiniowane zachowanie. Gdyby przypisanie nie było sekwencją przed obliczeniem wartości wyrażenia przypisania i = j+=1, skutkowałoby to nieokreśloną wartością. Z tego samego akapitu, który cytujesz: „We wszystkich przypadkach przypisanie jest sekwencjonowane po obliczeniu wartości prawych i lewych operandów, a przed obliczeniem wartości wyrażenia przypisania”. Dlatego (i+=10)+=10jest dobrze zdefiniowany do zrobienia i += 10; i += 10;. Z drugiej strony (i+=10)+=(i+=10)jest UB.
bames53
2
Ponieważ zauważyłem, że między komentatorami jest jakaś różnica zdań, opublikowałem to jako osobne pytanie: stackoverflow.com/questions/10655290/…
NPE