postinkrementacja

0

Witam,
na co dzień programuję w Javie. Ale ostatnio spotkałem się z czymś ciekawym i prosiłbym o wytłumaczenie krok po kroku dlaczego C/C++ zachowuje się tak a nie inaczej:

int a = 1;
int b = a++ + a; 
// b == 2
int a = 1;
a = a++ + a; 
// a == 3

W javie, dla zdawałoby się tożsamych instrukcji, w obu przypadkach wartość wynosi 3. Ciekawi mnie jak działa C/C++ w analizowaniu tego typu wyrażeń.

0
Qbek napisał(a)

Witam,
na co dzień programuję w Javie. Ale ostatnio spotkałem się z czymś ciekawym i prosiłbym o wytłumaczenie krok po kroku dlaczego C/C++ zachowuje się tak a nie inaczej:

int a = 1;
int b = a++ + a; 
// b == 2

tutaj trzeba dodać, że po tej operacji a=2

  1. pierwsza czynność to dodanie a+a co daje nam wynik b=2, a później po przypisaniu dodatkowo wykonuje się a++, więc b=2 a a=2
int a = 1;
a = a++ + a; 
// a == 3

tutaj pierw mamy a=a+a i po wykonaniu tej operacji a++, co łącznie daje 3

0

i dla porównania jeszcze jedna wersja:

int a = 1;
int b = ++a + a; 
// a == 2
// b == 4 // nie kompilowałem, ale pewnie tak wyjdzie
0

Równie dobrze może wyjść 666, zachowanie jest niezdefiniowane.

0

Dziękuję za odpowiedź.

Jeśli dobrze zrozumiałem mogę pokusić się o następujące uogólnienie, że postinkrementacja ZAWSZE następuje po wykonaniu całej instrukcji, a nie po odpowiadającej atomowej operacji (jak jest np. w javie). Czy ktoś mógłby stwierdzić czy się za bardzo nie "zagalopowałem" i że zrozumiałem to poprawnie?

0

@Fanael już napisał, zachowanie jest niezdefiniowane.

0

nie "zagalopowałem" i że zrozumiałem to poprawnie?

Zagalopowałeś się i to bardzo .
Olej to i omijaj z daleka takie konstrukcje , dla zabawy można się trochę zastanowić..

0

uważam że działanie jest ściśle określone

jeżeli mamy:
a = a++ + a;
to rozwijamy to następująco:
a = a + a;
a = a + 1;

drugi przypadek:
a = ++a + a;
rozwijamy do:
a = a + 1;
a = a + a;

stąd wyniki są deterministyczne

0
mk761203 napisał(a)

uważam że działanie jest ściśle określone

Od kiedy? Kolejność ewaluacji argumentów (także argumentów operatorów) jest zależna od implementacji - prawe a może być równie dobrze zwartościowane przed co po wykonaniu inkrementacji.

mk761203 napisał(a)

stąd wyniki są deterministyczne

Stąd standard języka nazywa to zachowaniem niezdefiniowanym i nieprzewidywalnym...

0
mk761203 napisał(a)

uważam że działanie jest ściśle określone

jeżeli mamy:
a = a++ + a;
to rozwijamy to następująco:
a = a + a;
a = a + 1;

drugi przypadek:
a = ++a + a;
rozwijamy do:
a = a + 1;
a = a + a;

stąd wyniki są deterministyczne

nie, nie są. porównaj proszę, efekty kompilacji tego kodu pod g++, msvc, i jeszcze inny jakis wymyśl, i prawie na pewno bedziesz mial rozne wyniki.
Standard C++ NIE DEFINIUJE KOLEJNOSCI wykonywania pod wyrazen. W javie i C# jest ona zdefiniowana i przywykles z niej korzystac (podswiadomie pewnie). Tutaj tak nie jest. Kolejnosc wykonywania podwyrazen to cos kompletnie innego niz priorytety operatorow!

wezmy np. a = 4 oraz wyrazenie: a + --a + a++
priorytety oraz semantyka operatorow i/lub rozsadek mowi, ze najpierw ma byc predecrement, potem suma, potem postincrement. powiedzmy ze jest to ok.
ale - skad ma pochodzic wartosc A dla poszczegolnych trzech wyrazen? w C++ nie jest powiedziane skad. ma pochodzic ze zmiennej A, z wartosci A niestarszej niz z poprzednio wykonanego wyrazenia, ale konkretnie ktore co ma sie wykonac najpierw NIE jest powiedizane. na jednym kompilatorze, moze zostac to wykonane tak:

a bylo na stosie = 4
mam wykonac a + --a + a++
biore a, zapamietuje w rejestrze
biore a, dekrementuje, zapamietuje w rejestrze, zapisuje nowe a na stos
biore a, zapamietuje w rejestrze, inkrementuje, zapisuje nowe a na stos
suma rejestr+rejestr+rejestr
wynik = 10

na innym kompilatorze moze byc to zrobione tak:

a bylo na stosie = 4
mam wykonac a + --a + a++
dekrementuje a, zapisuje nowe a na stos, zapamietuje w rejestrze
biore a, zapamietuje w rejestrze
biore a, zapamietuje w rejestrze, inkrementuje, zapisuje nowe a na stos
suma rejestr+rejestr+rejestr
wynik = 9

itd itp. I oba kompilatory maja racje, oba kompilatory SĄ POPRAWNE. Tak jak powiedzial świętowit, równie dobrze mogłoby tutaj wyjść 666 i też to by było poprawne zachowanie kompilatora.

W przeciwienstwie do Javy i C#, programista C++ ma obowiazek pilnowac i kontrolowac, aby wyrazenia czastkowe w danym wyrazeniu trzymaly sie zasady: albo wielokrotnie czytasz i nie zmieniasz, albo jednokrotnie czytasz i zmieniasz. NIE WOLNO zmieniac i czytac wielokrotnie tej samej wartosci.

tak jak a++ + a + --a czy slynne x+++++x sa z definicji niepoprawne, tak samo niepoprawne jest np.:
int a,b,c, *x = &a, &y = b;
c = a++ + --*x + --y; // wieloodczyt i wielozmiana z A (poprzez X oraz Y), kolejnosc podwyrazen nieokreslona
c = funkcja(a++, *x++, --y); // wieloodczyt i wielozmiana A.. kolejnosc wykonywania podwyrazen nieokreslona (tu: podwyrazenie=wyliczenie wartosci argumentu)

mam nadzieje, że teraz rozumiesz, czemu należy potrzykroć sprawdzać na czym się operuje i jeżeli to możliwe, starać sie uzywać maksymlanie jedno-dwa ++/-- w jednej linii. niestety automatyczna statyczna weryfikacja kodu jest jeszcze za malo rozwinieta, aby kompilator mogl zglaszac wszystkie w/w wpadki jaki przynajmniej warningi.. niektore wydaje mi sie ze przy prostych wyrazeniach zglaszaja:) ale - jedynie wydaje mi sie.

0
quetzalcoatl napisał(a)

Tak jak powiedzial świętowit, równie dobrze mogłoby tutaj wyjść 666
Ej no, ja nie jestem Świętowit!

0

Ożesz, zasugerowałem się cyframi, przepraszam:)

1 użytkowników online, w tym zalogowanych: 0, gości: 1