Probabilmente la tua domanda non era: "Perché questi costrutti comportano un comportamento indefinito in C?". Probabilmente la tua domanda era "Perché questo codice (usando ++
) non mi ha dato il valore che mi aspettavo?", E qualcuno ha contrassegnato la tua domanda come duplicata e ti ha inviato qui.
Questa risposta cerca di rispondere a quella domanda: perché il tuo codice non ti ha dato la risposta che ti aspettavi e come puoi imparare a riconoscere (ed evitare) espressioni che non funzioneranno come previsto.
Presumo che tu abbia già sentito la definizione di base di C ++
e --
operatori e di come il modulo prefisso ++x
differisca dal modulo postfisso x++
. Ma questi operatori sono difficili da pensare, quindi per essere sicuri di aver capito, forse hai scritto un piccolo programma di test che coinvolge qualcosa di simile
int x = 5;
printf("%d %d %d\n", x, ++x, x++);
Ma, con tua sorpresa, questo programma non ti ha aiutato a capire - ha stampato un output strano, inaspettato, inspiegabile, suggerendo che forse ++
fa qualcosa di completamente diverso, non è affatto quello che pensavi avesse fatto.
O forse stai guardando un'espressione difficile da capire come
int x = 5;
x = x++ + ++x;
printf("%d\n", x);
Forse qualcuno ti ha dato quel codice come un puzzle. Anche questo codice non ha senso, specialmente se lo esegui - e se lo compili ed eseguilo con due compilatori diversi, avrai probabilmente due risposte diverse! Cosa succede con quello? Quale risposta è corretta? (E la risposta è che entrambi lo sono, o nessuno dei due lo è.)
Come hai sentito ormai, tutte queste espressioni sono indefinite , il che significa che il linguaggio C non fornisce alcuna garanzia su ciò che faranno. Questo è un risultato strano e sorprendente, perché probabilmente hai pensato che qualsiasi programma che potresti scrivere, purché compilato ed eseguito, genererebbe un output unico e ben definito. Ma nel caso di un comportamento indefinito, non è così.
Cosa rende indefinita un'espressione? Le espressioni coinvolgono ++
e sono --
sempre indefinite? Certo che no: si tratta di operatori utili e, se li usi correttamente, sono perfettamente definiti.
Per le espressioni di cui stiamo parlando, ciò che le rende indefinite è quando ci sono troppe cose in una volta, quando non siamo sicuri in quale ordine accadranno le cose, ma quando l'ordine conta per il risultato che otteniamo.
Torniamo ai due esempi che ho usato in questa risposta. Quando ho scritto
printf("%d %d %d\n", x, ++x, x++);
la domanda è, prima di chiamare printf
, il compilatore calcola il valore di x
first, o x++
, o forse ++x
? Ma risulta che non lo sappiamo . Non esiste una regola in C che dice che gli argomenti di una funzione vengono valutati da sinistra a destra, da destra a sinistra o in un altro ordine. Quindi non possiamo dire se il compilatore farà x
, poi ++x
, quindi x++
, o x++
allora ++x
allora x
, o qualche altro ordine. Ma l'ordine conta chiaramente, perché a seconda dell'ordine utilizzato dal compilatore, otterremo chiaramente risultati diversi stampati da printf
.
Che mi dici di questa folle espressione?
x = x++ + ++x;
Il problema con questa espressione è che contiene tre diversi tentativi di modificare il valore di x: (1) la x++
parte tenta di aggiungere 1 a x, memorizzare il nuovo valore x
e restituire il vecchio valore di x
; (2) la ++x
parte tenta di aggiungere 1 a x, memorizza il nuovo valore x
e restituisce il nuovo valore di x
; e (3) la x =
parte tenta di assegnare la somma degli altri due a x. Quale di questi tre tentativi di assegnazione "vincerà"? A quale dei tre valori verrà assegnato x
? Ancora una volta, e forse sorprendentemente, in C non ci sono regole da dirci.
Potresti immaginare che la precedenza o l'associatività o la valutazione da sinistra a destra ti dicano in che ordine accadono le cose, ma non lo fanno. Potresti non credermi, ma per favore prendi la mia parola e la ripeterò: precedenza e associatività non determinano tutti gli aspetti dell'ordine di valutazione di un'espressione in C. In particolare, se all'interno di un'espressione ci sono più punti diversi in cui proviamo ad assegnare un nuovo valore a qualcosa come x
, la precedenza e l'associatività non ci dicono quale di questi tentativi avvenga per primo, per ultimo o altro.
Quindi, con tutto quel background e introduzione fuori mano, se vuoi assicurarti che tutti i tuoi programmi siano ben definiti, quali espressioni puoi scrivere e quali non puoi scrivere?
Queste espressioni vanno tutte bene:
y = x++;
z = x++ + y++;
x = x + 1;
x = a[i++];
x = a[i++] + b[j++];
x[i++] = a[j++] + b[k++];
x = *p++;
x = *p++ + *q++;
Queste espressioni sono tutte indefinite:
x = x++;
x = x++ + ++x;
y = x + x++;
a[i] = i++;
a[i++] = i;
printf("%d %d %d\n", x, ++x, x++);
E l'ultima domanda è: come puoi dire quali espressioni sono ben definite e quali espressioni non sono definite?
Come ho detto prima, le espressioni indefinite sono quelle in cui ci sono troppe cose in una volta, dove non si può essere sicuri in quale ordine accadano le cose e dove l'ordine conta:
- Se esiste una variabile che viene modificata (assegnata a) in due o più posizioni diverse, come fai a sapere quale modifica si verifica per prima?
- Se c'è una variabile che viene modificata in un posto e che utilizza il suo valore in un altro posto, come fai a sapere se utilizza il vecchio valore o il nuovo valore?
Come esempio del n. 1, nell'espressione
x = x++ + ++x;
ci sono tre tentativi di modificare `x.
Come esempio del n. 2, nell'espressione
y = x + x++;
entrambi utilizziamo il valore x
e lo modifichiamo.
Quindi questa è la risposta: assicurati che in ogni espressione che scrivi, ogni variabile venga modificata al massimo una volta e se una variabile viene modificata, non provi anche a usare il valore di quella variabile da qualche altra parte.