Cosa significa i = (i, ++ i, 1) + 1; fare?


174

Dopo aver letto questa risposta su comportamenti indefiniti e punti di sequenza, ho scritto un piccolo programma:

#include <stdio.h>

int main(void) {
  int i = 5;
  i = (i, ++i, 1) + 1;
  printf("%d\n", i);
  return 0;
}

L'output è 2. Oh Dio, non ho visto arrivare il declino! Cosa sta succedendo qui?

Inoltre, durante la compilazione del codice precedente, ho ricevuto un avviso che diceva:

px.c: 5: 8: avviso: l'operando di sinistra dell'espressione virgola non ha alcun effetto

  [-Wunused-value]   i = (i, ++i, 1) + 1;
                        ^

Perché? Ma probabilmente riceverà automaticamente risposta dalla mia prima domanda.


289
Non fare cose strane, non avrai amici :(
Maroun,

9
Il messaggio di avviso è la risposta alla tua prima domanda.
Yu Hao,

2
@gsamaras: no. il valore risultante viene scartato, non la modifica. la vera risposta: l'operatore virgola crea un punto sequenza.
Karoly Horvath,

3
@gsamaras Non dovresti preoccuparti di avere un punteggio positivo e anche di più con 10+ domande.
LyingOnTheSky

9
Nota: un compilatore di ottimizzazione può fare semplicemente printf("2\n");
chux - Ripristinare Monica il

Risposte:


256

Nell'espressione (i, ++i, 1), la virgola utilizzata è l' operatore virgola

l'operatore virgola (rappresentato dal token ,) è un operatore binario che valuta il suo primo operando e scarta il risultato, quindi valuta il secondo operando e restituisce questo valore (e tipo).

Poiché scarta il suo primo operando, è generalmente utile solo quando il primo operando ha effetti collaterali desiderabili . Se l'effetto collaterale sul primo operando non si verifica, il compilatore può generare un avviso sull'espressione senza alcun effetto.

Quindi, nell'espressione precedente, iverrà valutata la parte più a sinistra e il suo valore verrà scartato. Quindi ++iverrà valutato e incrementerà idi 1 e ancora il valore dell'espressione ++iverrà scartato, ma l'effetto collaterale iè permanente . Quindi 1verrà valutato e il valore dell'espressione sarà 1.

È equivalente a

i;          // Evaluate i and discard its value. This has no effect.
++i;        // Evaluate i and increment it by 1 and discard the value of expression ++i
i = 1 + 1;  

Si noti che l'espressione sopra è perfettamente valida e non invoca un comportamento indefinito perché esiste un punto di sequenza tra la valutazione degli operandi sinistro e destro dell'operatore virgola.


1
sebbene l'espressione finale sia valida, la seconda espressione ++ i non è un comportamento indefinito? viene valutato e il valore della variabile non inizializzata viene pre-incrementato, che non è giusto? Oppure mi sfugge qualcosa?
Koushik Shetty il

2
@Koushik; iè inizializzato con 5. Guarda la dichiarazione int i = 5;.
Hawcks

1
oh mio male. Mi dispiace, sinceramente, non lo vedo.
Koushik Shetty,

C'è un errore qui: ++ aumenterò, quindi lo valuterò, mentre i ++ valuterò, quindi lo incrementerò.
Quentin Hayot,

1
@QuentinHayot; Che cosa? Eventuali effetti collaterali si verificano dopo la valutazione dell'espressione. In questo caso ++i, questa espressione verrà valutata, iverrà incrementata e questo valore incrementato sarà il valore dell'espressione. In questo caso i++, questa espressione verrà valutata, il vecchio valore di isarà il valore dell'espressione, iverrà incrementato in qualsiasi momento tra il punto di sequenza precedente e successivo dell'espressione.
Hawcks

62

Citando da C11, capitolo 6.5.17, Operatore virgola

L'operando di sinistra di un operatore virgola viene valutato come espressione nulla; esiste un punto di sequenza tra la sua valutazione e quella dell'operando giusto. Quindi viene valutato l'operando giusto; il risultato ha il suo tipo e valore.

Quindi, nel tuo caso,

(i, ++i, 1)

viene valutato come

  1. i, viene valutato come espressione nulla, valore scartato
  2. ++i, viene valutato come espressione nulla, valore scartato
  3. infine, il 1valore restituito.

Quindi, la dichiarazione finale sembra

i = 1 + 1;

e iarriva a 2. Immagino che questo risponda ad entrambe le tue domande,

  • Come i ottiene un valore 2?
  • Perché c'è un messaggio di avviso?

Nota: FWIW, poiché è presente un punto di sequenza dopo la valutazione dell'operando di sinistra, un'espressione simile (i, ++i, 1)non invocherà UB, come si potrebbe generalmente pensare per errore.


+1 Sourav, poiché questo spiega perché l'intializzazione di ichiaramente non ha alcun effetto! Tuttavia, non penso che fosse così ovvio per un ragazzo che non conosce l'operatore virgola (e non sapevo come cercare aiuto, oltre a fare una domanda). Peccato che ho avuto così tanti voti negativi! Controllerò le altre risposte e deciderò quale accettare. Grazie! Bella risposta top tra l'altro.
gsamaras,

Sento di dover spiegare perché ho accettato la risposta di Hawcks. Ero pronto ad accettare le tue, dal momento che risponde davvero a entrambe le mie domande. Tuttavia, se controlli i commenti della mia domanda, vedrai che alcune persone non possono vedere a prima vista perché questo non invoca UB. Le risposte di Haccks forniscono alcune informazioni pertinenti. Certo, ho la risposta riguardo UB collegata alla mia domanda, ma alcune persone potrebbero non vederlo. Spero che tu sia d'accordo con la mia decisione, se non fammi sapere. :)
gsamaras,

30
i = (i, ++i, 1) + 1;

Analizziamolo passo dopo passo.

(i,   // is evaluated but ignored, there are other expressions after comma
++i,  // i is updated but the resulting value is ignored too
1)    // this value is finally used
+ 1   // 1 is added to the previous value 1

Quindi otteniamo 2. E il compito finale ora:

i = 2;

Qualunque cosa fosse in me prima che sia sovrascritta ora.


Sarebbe bello affermare che ciò accade a causa dell'operatore virgola. +1 per l'analisi dettagliata però! Bella risposta top tra l'altro.
gsamaras,

Mi dispiace per la spiegazione insufficiente, ho solo una nota lì ( ... ma ignorato, ci sono ... ). Volevo spiegare principalmente perché ++iciò non contribuisce al risultato.
dlask,

ora il mio for loop sarà sempre simile aint i = 0; for( ;(++i, i<max); )
CoffeDeveloper

19

Il risultato di

(i, ++i, 1)

è

1

Per

(i,++i,1) 

la valutazione avviene in modo tale che l' ,operatore scarti il ​​valore valutato e manterrà il valore più giusto1

Così

i = 1 + 1 = 2

1
Sì, ci ho pensato anche io, ma non so perché!
gsamaras,

@gsamaras perché l'operatore virgola valuta il termine precedente ma lo scarta (ovvero non lo usa per incarichi o simili)
Marco A.

14

Troverai delle buone letture sulla pagina wiki dell'operatore Comma .

Fondamentalmente

... valuta il suo primo operando e scarta il risultato, quindi valuta il secondo operando e restituisce questo valore (e tipo).

Ciò significa che

(i, i++, 1)

a sua volta, valuterà i, scarterà il risultato, valuterà i++, scarterà il risultato, quindi valuterà e restituirà 1.


O_O diavolo, fa che la sintassi è valida in C ++, ricordo che avevo pochi posti in cui avevo bisogno di quella sintassi (in pratica ho scritto: (void)exp; a= exp2;mentre avevo solo bisogno a = exp, exp2;)
CoffeDeveloper

13

Devi sapere cosa sta facendo l'operatore virgola qui:

La tua espressione:

(i, ++i, 1)

iViene valutata la prima espressione, la seconda espressione ++i, e la terza espressione,1 viene restituita per l'intera espressione.

Quindi il risultato è: i = 1 + 1 .

Per la tua domanda bonus, come vedi, la prima espressione inon ha alcun effetto, quindi il compilatore si lamenta.


5

La virgola ha una precedenza "inversa". Questo è ciò che otterrai da vecchi libri e manuali C di IBM (anni '70 / '80). Quindi l'ultimo 'comando' è quello che viene usato nell'espressione genitore.

Nella C moderna il suo uso è strano ma è molto interessante nella vecchia C (ANSI):

do { 
    /* bla bla bla, consider conditional flow with several continue's */
} while ( prepAnything(), doSomethingElse(), logic_operation);

Mentre tutte le operazioni (funzioni) vengono chiamate da sinistra a destra, solo l'ultima espressione verrà utilizzata come risultato per "while" condizionale. Questo impedisce la gestione di 'goto's per mantenere un blocco unico di comandi da eseguire prima del controllo delle condizioni.

EDIT: Questo evita anche una chiamata a una funzione di gestione che potrebbe occuparsi di tutta la logica degli operandi di sinistra e quindi restituire il risultato logico. Ricorda che, in passato non avevamo una funzione inline. Quindi, questo potrebbe evitare un overhead di chiamata.


Luciano, hai anche il link a questa risposta: stackoverflow.com/questions/17902992/… .
gsamaras,

Nei primi anni '90 prima delle funzioni in linea, l'ho usato molto per ottimizzare e mantenere organizzato il codice.
Luciano,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.