Valutazione delle Espressioni in Linguaggio C

Avendo visto come funzionano vari tipi di operatori aritmetici e di assegnamento, è venuto il momento di analizzare come le espressioni complesse che contengono tali operatori vengono valutate dal linguaggio C.

In questa lezione vedremo che gli operatori hanno una precedenza e che il linguaggio C valuta le espressioni secondo un ordine preciso. Inoltre è possibile forzare il linguaggio C a valutare un'espressione secondo un ordine diverso usando le parentesi, allo stesso modo delle espressioni matematiche.

Ordine di Valutazione delle Espressioni

Il linguaggio C come molti altri linguaggi di programmazione, valuta le espressioni secondo un ordine preciso. L'ordine è determinato dalla precedenza degli operatori. In altre parole, nelle espressioni complesse il linguaggio C valuta prima gli operatori di maggiore precedenza e poi quelli di minore precedenza.

Finora abbiamo visto gli operatori aritmetici, gli operatori di assegnamento e gli operatori di incremento e decremento. Di seguito è riportata una tabella parziale che mostra l'ordine di precedenza degli operatori e la loro associatività:

Operatore Sintassi Precedenza Associatività
Incremento (postfisso) ++ 5 A sinistra
Decremento (postfisso) -- 5 A sinistra
Incremento (prefisso) ++ 4 A destra
Decremento (prefisso) -- 4 A destra
Più (unario) + 4 A destra
Meno (unario) - 4 A destra
Moltiplicazione * 3 A sinistra
Divisione / 3 A sinistra
Modulo % 3 A sinistra
Somma + 2 A sinistra
Sottrazione - 2 A sinistra
Assegnamento = 1 A destra
Assegnamenti composti +=, -=, *=, /=, %= 1 A destra
Tabella 1: Ordine di precedenza parziale degli operatori aritmetici e di assegnamento in linguaggio C

Come si può notare dalla tabella, il valore di precedenza degli operatori va da 1 (più basso) a 5 (più alto). Ciò significa che, nel valutare un'espressione, il linguaggio C valuta prima gli operatori di maggiore precedenza e poi quelli di minore precedenza. Inoltre, se due operatori hanno la stessa precedenza, il linguaggio C valuta prima quelli che sono a sinistra e poi quelli che sono a destra.

La tabella di sopra è parziale, mancano altri operatori che verranno visti in seguito.

Per comprendere meglio, vediamo qualche esempio. Esaminiamo l'espressione che segue:

x = y += ++z - w++ * -2;

L'espressione è composta da operatori di assegnamento, operatori aritmetici e operatori di incremento. Per prima cosa, il linguaggio C valuta l'operatore di incremento postfisso su w, per cui l'espressione può essere riscritta come:

x = y += ++z - (w++) * -2;

Successivamente, il compilatore valuta gli operatori di incremento prefisso e meno unario, per cui l'espressione può essere riscritta come:

x = y += (++z) - (w++) * (-2);

Dopodiché, il compilatore valuta gli operatori di moltiplicazione e sottrazione, per cui l'espressione può essere riscritta come:

x = y += (++z) - ((w++) * (-2));

In seguito, verrà valutato l'operatore di sottrazione:

x = y += ((++z) - ((w++) * (-2)));

Infine, verranno valutati gli operatori di assegnamento:

x = (y += ((++z) - ((w++) * (-2))));

Adesso che abbiamo analizzato l'espressione, proviamo ad assegnare dei valori iniziali alle variabili y, z e w e valutare l'espressione:

int z = 1, w = 2, y = 5;

Seguendo l'ordine abbiamo che:

  1. w++ valuta w a 2 e poi incrementa w a 3; Tuttavia nell'espressione verrà usato il valore di w prima dell'incremento, quindi w sarà 2 nell'espressione:

    x = (y += ((++z) - (2 * (-2))));
    w = 3;
    
  2. ++z incrementa z a 2 e poi lo usa nell'espressione, trattandosi di un operatore prefisso:

    x = (y += ((2) - (2 * (-2))));
    
  3. -2 è un operatore unario, quindi viene valutato prima dell'operatore di moltiplicazione:

    x = (y += ((2) - (2 * (-2))));
    
  4. 2 * (-2) è un'espressione composta da un operatore di moltiplicazione, quindi viene valutata prima dell'operatore di sottrazione:

    x = (y += ((2) - (-4)));
    
  5. 2 - (-4) è un'espressione composta da un operatore di sottrazione, quindi viene valutata prima dell'operatore di assegnamento:

    x = (y += 6);
    
  6. Viene valutato dapprima l'operatore di assegnamento composto:

    y = 11;
    x = y;
    
  7. Infine, viene valutato l'operatore di assegnamento x = y.

Al termine della valutazione dell'espressione le variabili avranno i valori seguenti:

z = 2;
w = 3;
y = 11;
x = 11;

In ogni caso, sebbene il linguaggio C definisca un ordine di precedenza degli operatori, è sempre preferibile usare le parentesi per rendere più leggibile il codice.

Consiglio

Nel dubbio usare le parentesi

Quando bisogna realizzare un'espressione complessa ma non si ricorda bene l'ordine di precedenza degli operatori, è buona norma per rendere più leggibile il codice usare le parentesi.

Consiglio

Evitare di combinare assegnamenti e operazioni aritmetiche nella stessa espressione

Un'altra buona norma di programmazione in C è evitare di combinare più assegnamenti e operazioni aritmetiche nella stessa espressione. Questo perché, se l'espressione è complessa, potrebbe essere difficile capire quale operazione viene eseguita prima e quale dopo.

Ad esempio, conviene trasformare l'espressione che segue:

x = y += ++z - w++ * -2;

in:

++z;
y += z - w * -2;
x = y;
w++;

Precedenza e Sotto-Espressioni

Nel paragrafo precedente abbiamo visto che gli operatori hanno una precedenza e un'associatività in linguaggio C. Tuttavia, si possono usare sempre le parentesi per modificare quest'ordine in maniera del tutto analoga a quanto accade per un'espressione matematica.

Ad esempio, volendo effettuare un'addizione prima di una moltiplicazione possiamo scrivere:

int x = (2 + 3) * 4;

In questo caso, il risultato sarà 20 perché la somma viene eseguita prima della moltiplicazione.

Definizione

Parentesi e Sotto-Espressioni

In linguaggio C, così come nelle espressioni matematiche, le parentesi possono essere usate per modificare l'ordine di valutazione degli operatori.

Una sotto-espressione è un'espressione che è inclusa tra parentesi ed ha una precedenza superiore a qualsiasi operatore esterno.

Tuttavia, le parentesi e le sotto-espressioni presentano un'insidia. Infatti, in linguaggio C l'ordine di valutazione di due sotto-espressioni con la stessa precedenza non è definito.

Vediamo con un esempio:

x = (a + b) * (c + d);

In questa espressione le due sotto-espressioni a + b e c + d hanno la stessa precedenza. Tuttavia, l'ordine di valutazione di queste sotto-espressioni non è definito. Questo significa che il compilatore può valutarle in qualsiasi ordine. Potrebbe, cioè, valutare dapprima c + d e poi a + b oppure a + b e poi c + d.

Fintanto che si tratta di espressioni matematiche senza effetti collaterali il problema non si pone. Infatti, in questo caso, l'ordine di valutazione non influisce sul risultato. Tuttavia nel momento in cui combiniamo sotto-espressioni e assegnamenti il risultato è indefinito.

Ad esempio, il risultato di questa espressione non è definito:

x = (a += 1) * (a = b + 1);

In questo caso, il compilatore potrebbe valutare a += 1 e poi a = b + 1 oppure a = b + 1 e poi a += 1. In entrambi i casi il risultato è diverso.

Nota

Evitare di combinare sotto-espressioni e assegnamenti

Quando si realizza un programma in C è buona norma evitare di combinare sotto-espressioni e assegnamenti. La motivazione è che l'ordine di valutazione delle sotto-espressioni con la stessa precedenza non è definito.

In Sintesi

In questa lezione abbiamo visto che gli operatori hanno una precedenza e un'associatività in linguaggio C. Tuttavia, si possono usare sempre le parentesi per modificare quest'ordine in maniera del tutto analoga a quanto accade per un'espressione matematica.

L'utilizzo delle parentesi porta alla creazione di sotto-espressioni. Queste sono espressioni che sono incluse tra parentesi ed hanno una precedenza superiore a qualsiasi operatore esterno.

Tuttavia, le parentesi e le sotto-espressioni presentano un'insidia. Infatti, in linguaggio C l'ordine di valutazione di due sotto-espressioni con la stessa precedenza non è definito.