Nel C gli operatori hanno una precedenza definita come viene riportato
nella tabella seguente in ordine decrescente di priorita'
Precedenze fra Operatori
Operatori
Associativita'
() [] -> .
->
da sinistra a destra
! ~ ++ -- + - & * (tipo) sizeof
<-
da destra a sinistra
* / %
->
da sinistra a destra
+ -
->
da sinistra a destra
<< >>
->
da sinistra a destra
< <= > >=
->
da sinistra a destra
== !=
->
da sinistra a destra
&
->
da sinistra a destra
^
->
da sinistra a destra
|
->
da sinistra a destra
&&
->
da sinistra a destra
||
->
da sinistra a destra
?:
<-
da destra a sinistra
= += -= *= /= %= &= ^= |= <<= >>=
<-
da destra a sinistra
,
->
da sinistra a destra
Ordine di valutazione
Il C non specifica l'ordine di valutazione delle espressioni sulle quali
un operatore viene applicato. Per esempio, data l'espressione:
espr_1 op espr_2
il C lascia indefinito se verra' valutata prima
espr_1 o prima
espr_2.
Questo fatto puo' avere ripercussioni qualora si utilizzino espressioni
con effetti collaterali (side effect).
Esempio:
i=0;
c=a[i]+b[++i]; /* c = a[0] + b[1] OPPURE c = a[1] + b[1] ? */
La risposta dell'esempio dipende dalla versione del compilatore!!!
In pratica sono da evitare le espressioni che producano effetti
collaterali sulle variabili utilizzate anche in altre parti della medesima
espressione.
Associativita' degli operatori
Il linguaggio C definisce, invece, l'ordine in cui vengono associati
operatori diversi aventi la stessa priorita'.
Per esempio:
int *p;
int b[10];
p = b; /* *p equivale a b[0] */
*p++ = 5; /* b[0] = 5; *p equivale a b[1] */
p = b;
*++p = 5; /* b[1] = 5; *p equivale a b[1] */
p = b;
*p = 5; /* b[0] = 5 */
b[1] = ++*p; /* b[1] = 5; *b[0] = 6; *p equivale a b[0] */
Nell'esempio appena riportato, sia l'operatore di dereferimento * che
l'operatore di incremento ++ (sia che venga impiegato in forma postfissa,
che in forma prefissa), hanno la stessa priorita' nelle precedenze degli
operatori, ma la loro associativita' va da destra a sinistra.
Cio' comporta gli effetti sotto riportati.
Nel primo caso dell'esempio *p++ viene interpretato come *(p++),
pertanto l'operatore incremento (postfisso) e' applicato alla variabile
p (puntatore). L'effetto e' quello dell'utilizzo dell'area di memoria
puntata dal puntatore p (*p) ed in
seguito viene effettuato l'incremento del puntatore (p++).
Differente sarebbe l'effetto (*p)++ che, per forza di cose, non puo'
essere scritto senza l'uso delle parentesi. In questo caso, verrebbe
incrementato il contenuto (*p) a cui punta p, ma dopo del suo utilizzo
a causa dell'operatore di incremento ++ usato in forma postfissa.
Il caso *++p e' interpretato come *(++p), cioe' l'operatore di incremento
e' applicato a p in forma prefissa, pertanto prima viene incrementato il
puntatore p (++p), e poi (con il valore aggiornato del puntatore) viene
effettuato l'accesso all'area di memoria puntata da p incrementato.
Infine ++*p e equivalente a ++(*p), cioe' l'area di memoria puntata da
p viene acceduta subito con l'operatore di indirezione *. In seguito, ma
prima del suo utilizzo, l'area di memoria viene incrementata
dall'operatore di incremento ++ in forma prefissa.
In definitiva, si ha che:
Espressione
Equivale a:
Significato:
*p++
*(p++)
p viene incrementato dopo del suo utilizzo per accedere
alla memoria.
*++p
*(++p)
p viene incrementato prima del suo utilizzo per accedere
alla memoria.
++*p
++(*p)
p e' utilizzato per l'accesso alla memoria, ma il contenuto della
locazione viene incrementato prima del suo utilizzo.
(*p)++
(*p)++
p e' utilizzato per l'accesso alla memoria; il contenuto della
locazione viene incrementato dopo del suo utilizzo.
Conclusione: per una maggiore chiarezza (riscontrabile specialmente
durante le fasi di manutenzione di un programma) e' consigliabile l'uso
delle parentesi al fine di facilitarne la lettura e di evitare di far
ricorso a regole sintattiche tipiche del linguaggio.