2 TYPY, OPERATORY I WYRAŻENIA
struktury; będą szczegółowo opisane w rozdz. 6 razem z operatorem sizeof (rozmiar obiektu). W rozdziale 5 poznamy jednoargumentowe operatory * (dostęp pośredni za pomocą wskaźnika) oraz & (adres obiektu), a w rozdz. 3 zapoznamy się z operatorem! , (przecinek).
Zauważ, że priorytet bitowych operatorów &, oraz | jest niższy niż priorytet operatorów przyrównania == i !=. Wynika z tego, że wyrażenia testujące bity, np.
if ((x & MASK) == 0) ...
aby dawały poprawne wyniki, muszą zawierać odpowiednią liczbę nawiasów.
Tablica 2-1. Priorytety i łączność operatorów
Operatory |
Łączność |
o n -> ■ |
lewostrona |
! ~ -H- — + - * & (typ) sizeof |
prawostronna |
* / % |
lewostronna |
+ - |
lewostronna |
« » |
lewostronna |
<<=>>= |
lewostronna |
== != |
lewostronna |
& |
lewostronna |
lewostronna | |
i |
lewostronna |
&& |
lewostronna |
ii |
lewostronna |
?• • • |
prawostronna |
= += -= *= /= %= ~ = |= «= »= |
prawostronna |
• |
lewostronna |
Jednoargumentowe operatory +, * oraz & mają priorytet wyższy niż ich odpowiedniki
dwuargumentowe.
W języku C, jak w większości języków programowania, nie określa się kolejności o*| liczania wartości argumentów operatora. (Do wyjątków należą operatory &&, ||, oraz ’,’.) Na przykład w instrukcji
x = f() + g 0; 1
funkcja f może być wykonana przed funkcją g lub odwrotnie. A więc w przypadł gdy jedna z funkcji (f lub g)zmienia wartość zmiennej, od której zależy ta druga, w* tość zmiennej X może zależeć od kolejności wykonania tych funkcji. Aby zapewfl
Operator jest lewostronnie (prawostronnie) łączny, jeżeli w wyrażeniu zawierającym co najmniej dwa operatory na tym samym poziomie struktury nawiasowej najpierw jest wykonywany operator lewy (pra^ Operator jest łączny, jeżeli kolejność wykonywania jest dowolna. - Przyp. tłum.
2.12 PRIORYTETY I KOLEJNOŚĆ OBLICZEŃ
powered oy
szczególną kolejność obliczeń, wyniki pośrednie można przechowywać w zmiennych tymczasowych.
Podobnie kolejność obliczania wartości argumentów funkcji nic jest określona, toteż instrukcja
printf(”%d %d\n”, -h-n, power(2,n)); /* ŹLE */
może dla różnych kompilatorów produkować różne wyniki zależnie od tego, czy n jest zwiększane przed czy po wywołaniu funkcji power. Rozwiązaniem jest oczywiście napisanie programu inaczej, np.
++n;
printf(”%d %d\n‘\ n, power(2,n));
Wywołania funkcji, zagnieżdżone instrukcje przypisania oraz operatory zwiększania i zmniejszania powodują „efekty uboczne” - przy okazji obliczania wyrażenia pewna zmienna otrzymuje nową wartość. W wyrażeniu powodującym efekty uboczne może pojawić się subtelna zależność od kolejności aktualizacji wartości zmiennych biorących udział w obliczeniach. Ilustracją takiej niefortunnej sytuacji jest instrukcja
a[i] = i++;
Pytanie brzmi: czy indeks tej tablicy jest starą wartością i czy nową? Kompilatory mogą przetłumaczyć tę instrukcję na wiele sposobów i wygenerować różne odpowiedzi według własnej interpretacji. W standardzie specjalnie nic uściślano tego rodzaju spraw. To, kiedy w wyrażeniu nastąpi efekt uboczny (przypisanie wartości zmiennej), pozostawia się decyzji kompilatora, ponieważ najlepsza kolejność obliczeń ściśle zależy od architektury maszyny. (Jednocześnie w standardzie stwierdzono, że wszystkie efekty uboczne obliczenia argumentów funkcji muszą mieć miejsce przed jej wywołaniem, ale to niewiele pomaga w powyższym wywołaniu funkcji printf.)
Morał z tej dyskusji jest następujący: pisanie programów zależnych od kolejności wykonywania obliczeń należy do złej praktyki programowania w każdym języku. Naturalnie trzeba wiedzieć, czego unikać, lecz jeśli nie wiesz, jak pewne rzeczy zostały zrobione na różnych maszynach, nie będzie Cię kusiło, aby wykorzystywać specyfikę konkretnej implementacji.