Come funziona un uguale espressione in un segnaposto di printf?

Ho il seguente frammento di codice:

main( ) { int k = 35 ; printf ( "\n%d %d %d", k == 35, k = 50, k > 40 ) ; } 

che produce il seguente output

 0 50 0 

Non sono sicuro di capire come il primo valore di printf arriva a 0 . Quando il valore di k viene confrontato con 35 , dovrebbe idealmente restituire (e quindi stampare) 1, ma come si stampa zero? Gli altri due valori prodotti – 50 e 0 vanno bene, perché nel secondo valore, il valore di k è preso come 50 , e per il terzo valore – il valore di k (che è 35 ) viene confrontato con 40 . Dal 35 < 40 , quindi stampa 0.

Qualsiasi aiuto sarebbe apprezzato, grazie.

**AGGIORNARE**

Dopo aver fatto ulteriori ricerche su questo argomento e anche su un undefined behavior , mi sono imbattuto in un libro su C, la fonte è data alla fine.

Calling Convention La convenzione di chiamata indica l’ordine in cui gli argomenti vengono passati a una funzione quando viene rilevata una chiamata di funzione. Ci sono due possibilità qui:

  1. Gli argomenti potrebbero essere passati da sinistra a destra.
  2. Gli argomenti potrebbero essere passati da destra a sinistra.

Il linguaggio C segue il secondo ordine.

Si consideri la seguente chiamata di funzione:

 fun (a, b, c, d ) ; 

In questa chiamata non importa se gli argomenti vengono passati da sinistra a destra o da destra a sinistra. Tuttavia, in alcune funzioni, l’ordine degli argomenti che passano diventa una considerazione importante. Per esempio:

 int a = 1 ; printf ( "%d %d %d", a, ++a, a++ ) ; 

Sembra che questo printf( ) restituisca 1 2 3 . Questo però non è il caso. Sorprendentemente, emette 3 3 1 .

Questo perché la convenzione di chiamata di C va da right to left . Cioè, in primo luogo 1 viene passato attraverso l’espressione a++ e quindi a viene incrementato a 2 . Quindi viene passato il risultato di ++a . Cioè, a viene incrementato a 3 e quindi passato. Infine, viene passato l’ultimo valore di a , ovvero 3 . Quindi, right to left order 1, 3, 3 vengono passati 1, 3, 3 . Una volta che printf( ) li colleziona, li stampa nell’ordine in cui gli abbiamo chiesto di stamparli (e non nell’ordine in cui sono stati passati). Quindi 3 3 1 viene stampato.

 **Source: "Let Us C" 5th edition, Author: Yashwant Kanetkar, Chapter 5: Functions and Pointers** 

Indipendentemente dal fatto che questa domanda sia un duplicato o meno, ho trovato questa nuova informazione utile per me, quindi ho deciso di condividere. Nota: questo supporta anche il reclamo presentato da Mr.Zurg nei commenti seguenti.

Sfortunatamente per tutti quelli che hanno letto quel libro è totalmente sbagliato. La bozza dello standard C99 rende chiaramente questo comportamento indefinito del codice. Un rapido controllo con la voce di Wikipedia sul comportamento non definito contiene un esempio simile e lo identifica come non definito. Non lo lascerò ma ci sono altre fonti facilmente accessibili che ottengono questo diritto senza dover ricorrere allo standard.

Quindi cosa dice lo standard? Nella sezione 6.5 Espressioni il paragrafo 3 dice:

Il raggruppamento di operatori e operandi è indicato dalla syntax.74) Tranne come specificato in seguito (per gli operatori function-call (), &&, ||,?:, E virgola), l’ordine di valutazione delle sottoespressioni e l’ordine in quali effetti collaterali hanno luogo sono entrambi non specificati.

Quindi, se non specificato l’ordine di valutazione delle sottoespressioni non è specificato, e l’ulteriore paragrafo 6.5.2.2 Function chiama il paragrafo 10 dice:

L’ordine di valutazione del designatore di funzioni, degli argomenti effettivi e delle sottoespressioni all’interno degli argomenti effettivi non è specificato, ma esiste un punto di sequenza prima della chiamata effettiva.

Quindi nel tuo esempio:

 printf ( "\n%d %d %d", k == 35, k = 50, k > 40 ) ; ^ ^ ^ ^ 1 2 3 4 

la sotto-espressione da 1 to 4 potrebbe essere valutata in qualsiasi ordine e non abbiamo modo di sapere quando gli effetti collaterali di ogni sotto-espressione avranno luogo sebbene sappiamo che tutti devono avere effetto prima che la funzione venga effettivamente chiamata.

Quindi k = 50 potrebbe essere valutato prima o l’ultima o in qualsiasi punto intermedio e indipendentemente da quando viene valutato l’effetto collaterale della modifica del valore di k potrebbe avvenire immediatamente dopo o meno fino all’esecuzione della funzione effettiva. Il che significa che i risultati sono imprevedibili e potrebbero in teoria modificare i cambiamenti durante le diverse esecuzioni.

Quindi, dobbiamo affrontare come questo comportamento diventa indefinito. Questo è trattato nella sezione 6.5 paragrafo 2 che dice:

Tra il punto di sequenza precedente e quello successivo un object deve avere il suo valore memorizzato modificato al massimo una volta dalla valutazione di un’espressione.72) Inoltre, il valore precedente deve essere letto solo per determinare il valore da memorizzare.73)

Quindi nel tuo esempio non stiamo modificando k più di una volta, ma stiamo usando il valore precedente per fare altre cose oltre a determinare il valore da memorizzare. Quindi quali sono le implicazioni del comportamento indefinito? Nella definizione di comportamento indefinito lo standard dice:

Il ansible comportamento indefinito va dall’ignorare completamente la situazione con risultati imprevedibili, a comportarsi durante la traduzione o l’esecuzione del programma in un modo documentato caratteristico dell’ambiente (con o senza emissione di un messaggio diagnostico), a terminare una traduzione o un’esecuzione (con l’emissione di un messaggio diagnostico).

Quindi il compilatore potrebbe ignorarlo o potrebbe anche produrre risultati imprevedibili, che coprono una vasta gamma di comportamenti indesiderati. È stato tristemente detto che:

Quando il compilatore incontra [un dato costrutto indefinito] è legale che faccia volare fuori i demoni dal tuo naso

che mi sembra abbastanza indesiderabile.