[C++] Kolejność wartościowania argumentów


(Piki1) #1

W każdym z poniższych przypadków "cout<<..." Dev zwraca mi 1. Czy jest jakaś zasada kolejności wartościowania argumentów? Czy rezultaty są jednoznaczne (nie zależą od implementacji?)?

++t/++t => 11/12=>daje 1(mimo że jest to 0,..... dlaczego zaokrągla w górę?)

++t/--t => 11/10 => 1 (ok)

--t/++t => 9/10 => daje znów 1

--t/--t => 9/8 => daje 1 (ok)

#include 

using namespace std;

int main(int argc, char *argv[])

{int t=10;

cout<<++t/++t; //++t/--t //--t/++t /--t/--t <= cztery przypadki

    system("PAUSE");

    return EXIT_SUCCESS;

}

Ale gdy wykonałem działanie 5/6 (przy int) dało wynik 0 (poprawny). Więc proszę o wyjaśnienie, dlaczego otrzymałem takie wyniki?


(Sawyer47) #2

Kolejność obliczania operandów jest w większości przypadków zależna od implementacji z tego co pamiętam, więc takiego kodu nie powinno się pisać.


(Piki1) #3

Rozumiem, więc jeśli takie cos zobaczę, to powinienem napisać komentarz - wynik niejednoznaczny? Albo jeśli będą ode mnie chceli wyniku (Dev liczy wszystko), to mam wpisać 1??


(Sawyer47) #4

Zawsze kompiluj swój kod na najwyższych ustawieniach ostrzeżeń. Oto co wypluwa g++:

ble.cpp: In function ‘int main()’:

ble.cpp:6: warning: operation on ‘t’ may be undefined

Czyli jest to zachowanie niezdefiniowane.


(Piki1) #5

Czyli wywala ci błąd i nic nie liczy? Jednak stara prawda, że Dev wszystko policzy ma sens.


(Sawyer47) #6

Nie, daje ostrzeżenie, że jest to zachowanie niezdefiniowane i zależne od implementacji. Kompilować kompiluje się. Zobacz ten kod:

#include 

using namespace std;


struct smth {

	double operator()(double i) { std::cout << ++c << ": " << i << std::endl; return i; }

	static int c;

};


int smth::c = 0;


int main()

{

	smth t;

	double a = 10;

	cout << "A1: " << t(++a) / t(++a) << endl;

	a = 10;

	cout << "A2: " << t(--a) / t(++a) << endl;

	a = 10;

	cout << "A3: " << t(++a) / t(--a) << endl;

	a = 10;

	cout << "A4: " << t(--a) / t(--a) << endl;


	double b = 10;

	cout << "B1: " << ++b / ++b << endl;

	b = 10;

	cout << "B2: " << --b / ++b << endl;

	b = 10;

	cout << "B3: " << ++b / --b << endl;

	b = 10;

	cout << "B4: " << --b / --b << endl;

	return 0;

}

Najpierw daje poprawne wyniki (choć nadal wyrzuca ostrzeżenie, że wynik może być niezdefiniowany). W drugim przypadku są u mnie same 1, co pokazuje, że faktycznie wyrażenie w stylu ++t/++t jest niezdefiniowane, bo teoretycznie kod robi to samo, a wyniki różne. Wygląda to tak, np. dla t = 10 dla ++t/++t najpierw oblicza lewy operand (t == 11) i zamiast robić kopię zmiennej po prostu kompilator wie, że jej wartość będzie w zmiennej t, następnie oblicza prawy operand (t == 12) i analogicznie jak poprzednio, po co robić kopię, skoro wartość wyrażenia jest w zmiennej t? Następnie bierze wartości t, t/t == 1, najpewniej jest tak jak to opisałem


(Piki1) #7

Dobra wszystko ok, ale powiedz mi jedno. Dlaczego jak dzielę przez siebie liczby calkowite w tych podpunktach B wychodzą same 1, mimo że dzielę np 9/10? dlaczego zaokrągla w górę? Bo w podpunktach A dzielenie liczb calkowitych daje poprawne wyniki tzn. tam gdzie ma być 0 jest 0, a tam gdzie ma być 1 jest 1.

#include 

using namespace std;


struct smth {

   int operator()(int i) { std::cout << ++c << ": " << i << std::endl; return i; }

   static int c;

};


int smth::c = 0;


int main()

{

   smth t;

   int a = 10;

   cout << "A1: " << t(++a) / t(++a) << endl;//0

   a = 10;

   cout << "A2: " << t(--a) / t(++a) << endl;//0

   a = 10;

   cout << "A3: " << t(++a) / t(--a) << endl;//1

   a = 10;

   cout << "A4: " << t(--a) / t(--a) << endl;//1


   int b = 10;

   cout << "B1: " << ++b / ++b << endl;//1

   b = 10;

   cout << "B2: " << --b / ++b << endl;//1

   b = 10;

   cout << "B3: " << ++b / --b << endl;//1

   b = 10;

   cout << "B4: " << --b / --b << endl;//1

    system("PAUSE");

    return EXIT_SUCCESS;

}

(Sawyer47) #8

Przecież właśnie to napisałem. Najpewniej jest tak, że wartości wyrażeń są obliczane (czyli t zwiększa się o dwa), ale nie jest robiona kopia wartości wyrażenia ++t, po prostu 't' jest zwiększane dwukrotnie i później bezpośrednio program odwołuje się do wartości t, więc w rzeczywistości jest to tyle co t/t, czyli (prawie) zawsze 1. Jest to więc kolejno 12/12, 10/10, 10/10, 8/8.


(Piki1) #9

Ok, ale z --b/++b to nie rozumiem. jeżeli jest tak jak piszesz, to mamy: (10-1)/(10+1), czyli w double 0.9, a w int daje 1 (domyślne zaokrąglanie w górę?).


(Sawyer47) #10

Sęk w tym, że wartości wyrażeń ++t lub --t nie są kopiowane

Zobacz:

#include 


int divide(int a, int b)

{

	std::cout << "args (" << a << ", " << b << ")\n"; 

	return a / b;

}


int main()

{

	int t = 10;

	std::cout << divide(++t, ++t) << std::endl;

	t = 10;

	std::cout << divide(--t, ++t) << std::endl;

	return 0;

}

Najpierw obliczane są operandy (co oczywiste), ale wartości tych wyrażeń nie są kopiowane, funkcja/operator cały czas odwołują się do tego samego miejsca w pamięci, do zmiennej 't'. Co byś nie zrobił na 't' i tak ostatecznie będzie to wyrażenie t/t.