Domanda

#include <stdio.h>

int main(void)
{
   int i = 0;
   i = i++ + ++i;
   printf("%d\n", i); // 3

   i = 1;
   i = (i++);
   printf("%d\n", i); // 2 Should be 1, no ?

   volatile int u = 0;
   u = u++ + ++u;
   printf("%d\n", u); // 1

   u = 1;
   u = (u++);
   printf("%d\n", u); // 2 Should also be one, no ?

   register int v = 0;
   v = v++ + ++v;
   printf("%d\n", v); // 3 (Should be the same as u ?)

   int w = 0;
   printf("%d %d\n", ++w, w); // shouldn't this print 1 1

   int x[2] = { 5, 8 }, y = 0;
   x[y] = y ++;
   printf("%d %d\n", x[0], x[1]); // shouldn't this print 0 8? or 5 0?
}
È stato utile?

Soluzione

C è il concetto di comportamento indefinito, cioèalcuni costrutti del linguaggio sintatticamente valida ma non è possibile prevedere il comportamento quando viene eseguito il codice.

Per quanto ne so, lo standard non esplicitamente dire perché il concetto di comportamento indefinito esiste.Nella mia mente, è semplicemente perché la lingua designer ha voluto che ci sia qualche margine di manovra nella semantica, invece di cioèposto che tutte le implementazioni di gestire integer overflow nello stesso modo, che molto probabilmente imporre prestazioni gravi costi, hanno appena lasciato il comportamento indefinito, in modo che se si scrive codice che causa integer overflow, tutto può succedere.

Quindi, con questo in mente, perché sono questi "problemi"?La lingua dice chiaramente che certe cose fanno un comportamento indefinito.Non c'è nessun problema, non c'è nessun "dovrebbe" coinvolti.Se il comportamento indefinito cambia quando una delle variabili coinvolte è dichiarato volatile, che non prova o cambiare nulla.È undefined;è in grado di ragionare sul comportamento.

Il più interessante-looking esempio, quello con

u = (u++);

è un libro di testo di esempio di un comportamento indefinito (vedere la voce di Wikipedia sul sequenza di punti).

Altri suggerimenti

Basta compilare e smontare il linea di codice, se siete così inclini a sapere esattamente come è che si ottiene quello che si stanno ottenendo.

Questo è quello che ho sulla mia macchina, insieme a quello che penso sta succedendo:

$ cat evil.c
void evil(){
  int i = 0;
  i+= i++ + ++i;
}
$ gcc evil.c -c -o evil.bin
$ gdb evil.bin
(gdb) disassemble evil
Dump of assembler code for function evil:
   0x00000000 <+0>:   push   %ebp
   0x00000001 <+1>:   mov    %esp,%ebp
   0x00000003 <+3>:   sub    $0x10,%esp
   0x00000006 <+6>:   movl   $0x0,-0x4(%ebp)  // i = 0   i = 0
   0x0000000d <+13>:  addl   $0x1,-0x4(%ebp)  // i++     i = 1
   0x00000011 <+17>:  mov    -0x4(%ebp),%eax  // j = i   i = 1  j = 1
   0x00000014 <+20>:  add    %eax,%eax        // j += j  i = 1  j = 2
   0x00000016 <+22>:  add    %eax,-0x4(%ebp)  // i += j  i = 3
   0x00000019 <+25>:  addl   $0x1,-0x4(%ebp)  // i++     i = 4
   0x0000001d <+29>:  leave  
   0x0000001e <+30>:  ret
End of assembler dump.

(I ... suppongo che l'istruzione 0x00000014 fosse una specie di ottimizzazione del compilatore?)

Credo che le parti pertinenti della norma C99 stanno 6.5 Espressioni, §2

  

Tra il precedente e successivo punto sequenza un oggetto ha il suo valore memorizzato   modificato al massimo una volta dalla valutazione di un'espressione. Inoltre, il valore precedente   devono essere intesi solo per determinare il valore da memorizzare.

e 6.5.16 Gli operatori di assegnazione, § 4:

  

L'ordine di valutazione degli operandi è specificato. Se viene fatto un tentativo di modificare   il risultato di un operatore di assegnamento o per accedervi dopo il successivo punto di sequenza, la   comportamento non è definito.

Il comportamento non può essere spiegato perché richiama sia non specificato comportamento e un comportamento indefinito, in modo che possiamo non fare previsioni generali in merito al presente codice, anche se a leggere Olve Maudal s lavoro come Profonda C e Non specificato e Definito a volte si possono fare buoni tentativi in casi molto specifici, con uno specifico compilatore e ambiente, ma per favore non fare che da nessuna parte vicino la produzione.

Così di passare per non specificato comportamento, in progetto di standard c99 sezione6.5 paragrafo 3 dice(sottolineatura mia):

Il raggruppamento di operatori e operandi è indicato dalla sintassi.74) ad Eccezione di quanto specificato più tardi (per la funzione di chiamata (), &&, ||, ?:, e virgola operatori), l'ordine di valutazione delle espressioni e l'ordine in cui gli effetti collaterali sono entrambi non specificato.

Così, quando abbiamo una riga come questa:

i = i++ + ++i;

non sappiamo se i++ o ++i saranno valutati prima.Questo è principalmente quello di dare il compilatore migliori opzioni per l'ottimizzazione.

Abbiamo anche un comportamento indefinito qui, in quanto il programma di modifica di variabili(i, u, ecc..) più di una volta tra sequenza di punti.Dal progetto di sezione standard 6.5 paragrafo 2(sottolineatura mia):

Tra la precedente e la successiva sequenza di punto di un oggetto deve avere il suo valore memorizzato modificato più di una volta la valutazione di un'espressione.Inoltre, il valore precedente deve essere letto esclusivamente per determinare il valore da memorizzare.

si cita il codice riportato di seguito alcuni esempi come indefinito:

i = ++i + 1;
a[i++] = i; 

In tutti questi esempi, il codice è il tentativo di modificare un oggetto più di una volta nella stessa sequenza punto, che si concluderà con la ; in ognuno di questi casi:

i = i++ + ++i;
^   ^       ^

i = (i++);
^    ^

u = u++ + ++u;
^   ^       ^

u = (u++);
^    ^

v = v++ + ++v;
^   ^       ^

Non specificato comportamento è definito nel progetto di standard c99 nella sezione 3.4.4 come:

l'uso di un non meglio specificato valore, o altri comportamenti in cui questa Norma Internazionale fornisce due o più possibilità e non impone ulteriori requisiti per i quali viene scelto in qualsiasi istanza

e un comportamento indefinito è definito nella sezione 3.4.3 come:

comportamento, con l'uso di un nonportable o errata programma di costruire o di dati errati, per il quale la presente Norma Internazionale non impone requisiti

e note:

Possibile un comportamento indefinito varia da ignorare la situazione è completamente con risultati imprevedibili, a comportarsi durante la traduzione o l'esecuzione del programma in maniera documentata caratteristica dell'ambiente (con o senza l'emissione di un messaggio di diagnostica), a chiusura di una traduzione o di esecuzione (con l'emissione di un messaggio di diagnostica).

La maggior parte delle risposte qui citato da C di serie sottolineando che il comportamento di questi costrutti sono indefiniti. Per capire perché il comportamento di questi costrutti non sono definiti , cerchiamo di capire questi termini prima alla luce di standard di C11:

sequenziato: (5.1.2.3)

  

Dato qualsiasi due valutazioni A e B, se A viene sequenziato prima B, allora l'esecuzione di A deve precedere l'esecuzione di B.

non in sequenza:

  

Se A non viene sequenziato prima o dopo B, quindi A e B sono non in sequenza.

Le valutazioni possono essere di due cose:

  • calcoli valore , che risolvono il risultato di espressione; e
  • effetti collaterali , che sono modifiche di oggetti.

Sequenza Point:

  

La presenza di un punto di sequenza tra la valutazione delle espressioni A e B implica che ogni valore calcolo e effetto collaterale associato A viene sequenziato prima di ogni valore calcolo e effetto collaterale legato al B.

Ora venendo alla domanda, per le espressioni come

int i = 1;
i = i++;

norma dice che:

6.5 Espressioni:

  

Se un effetto collaterale su un oggetto scalare è non in sequenza rispetto alla un effetto collaterale diversa sullo stesso oggetto scalare o un calcolo valore utilizzando il valore dello stesso scalari oggetto, il comportamento è indefinito . [...]

Pertanto, l'espressione sopra invoca UB perché due effetti collaterali sullo stesso i oggetto è non in sequenza rispetto all'altro. Ciò significa che non è sequenziato se l'effetto collaterale mediante assegnazione di i sarà fatto prima o dopo l'effetto collaterale da ++.
A seconda che si verifica incarico prima o dopo l'incremento, risultati diversi saranno prodotti e questo è quello del caso di comportamento non definito .

Consente di rinominare il i a sinistra dell'assegnazione essere il e al diritto di assegnazione (in i++ espressione) essere ir, allora l'espressione essere come

il = ir++     // Note that suffix l and r are used for the sake of clarity.
              // Both il and ir represents the same object.  

Un importante punto riguardo Postfix operatore ++ è che:

  

solo perché il ++ viene dopo la variabile non significa che l'incremento avviene in ritardo . L'incremento può avvenire già a partire del calibro del compilatore fino a quando il compilatore garantisce che il valore originale viene utilizzato .

Si intende il il = ir++ espressione potrebbe essere valutata sia come

temp = ir;      // i = 1
ir = ir + 1;    // i = 2   side effect by ++ before assignment
il = temp;      // i = 1   result is 1  

o

temp = ir;      // i = 1
il = temp;      // i = 1   side effect by assignment before ++
ir = ir + 1;    // i = 2   result is 2  

conseguente due risultati diversi 1 e 2 che dipende dalla sequenza di effetti collaterali da assegnazione e ++ e quindi richiama UB.

Un altro modo di rispondere a questa, invece di impantanarsi in dettagli arcani di punti di sequenza e un comportamento indefinito, è semplicemente quello di chiedere, cosa stanno vorrebbe dire? Qual è stato il programmatore cercando di fare?

Il primo frammento chiesto, i = i++ + ++i, è abbastanza chiaramente folle nel mio libro. Nessuno avrebbe mai scriverlo in un programma vero e proprio, non è ovvio ciò che fa, non c'è concepibile algoritmo che qualcuno avrebbe potuto cercare di codice che avrebbe portato a questa particolare sequenza di operazioni artificioso. E dato che non è ovvio per voi e per me quello che si suppone di fare, va bene nel mio libro, se il compilatore non riesce a capire quello che si suppone di fare, sia.

Il secondo frammento, i = i++, è un po 'più facile da capire. Qualcuno sta chiaramente cercando di incrementare i, e assegnare il risultato di nuovo per i. Ma ci sono un paio di modi per farlo in C. Il modo più semplice per aggiungere 1 a I, e assegnare il risultato di nuovo per i, è la stessa in quasi qualsiasi linguaggio di programmazione:

i = i + 1

C, naturalmente, ha una comoda scorciatoia:

i++

Questo significa, "aggiungere 1 a I, e assegnare il risultato di nuovo per i". Quindi, se costruiamo un miscuglio dei due, scrivendo

i = i++

quello che stiamo veramente dire è "aggiungere 1 a I, e assegnare il risultato di nuovo per i, e assegnare il risultato di nuovo per i". Siamo confusi, in modo che non si preoccupa troppo di me molto se il compilatore si confonde, anche.

Realisticamente, l'unica volta che queste espressioni folli vengono scritti è quando le persone li utilizzano come esempi di come artificiali ++ dovrebbe funzionare. E, naturalmente, è importante capire come funziona ++. Ma una regola pratica per l'utilizzo di ++ è: "Se non è ovvio ciò che un espressione utilizzando ++ mezzi, non scriverlo".

Siamo abituati a trascorrere ore e ore in comp.lang.c discutere espressioni come queste e perché Sono indefinito. Due delle mie risposte più lunghe, che cercano di spiegare il motivo per cui, in realtà sono archiviati sul web:

interrogare 3.8 e il resto delle domande in sezione 3 della C FAQ lista .

Spesso questa domanda è legata come un duplicato di domande relative al codice come

printf("%d %d\n", i, i++);

o

printf("%d %d\n", ++i, i++);

o simili varianti.

Mentre questo è anche comportamenti indefiniti come detto già, ci sono sottili differenze quando printf() è coinvolto quando si confrontano un'istruzione del tipo:

x = i++ + i++;

Nel seguente dichiarazione:

printf("%d %d\n", ++i, i++);

il ordine di valutazione di argomenti in printf() è non specificato.Che significa, espressioni i++ e ++i potrebbe essere valutata in qualsiasi ordine. C11 standard ha alcune descrizioni pertinenti su questo:

Allegato J, non specificato comportamenti

L'ordine in cui la funzione di designazione, gli argomenti e sottoespressioni entro gli argomenti sono valutati in una chiamata di funzione (6.5.2.2).

3.4.4, non specificato comportamento

L'uso di un non meglio specificato valore, o altri in cui questo comportamento Internazionale Standard prevede due o più possibilità e impone no ulteriori requisiti per i quali viene scelto in ogni istanza.

Esempio Un ESEMPIO di un imprecisato comportamento è l'ordine in cui le gli argomenti di una funzione vengono valutati.

Il non specificato comportamento di per sé NON è un problema.Si consideri questo esempio:

printf("%d %d\n", ++x, y++);

Anche questo ha non specificato comportamento perché l'ordine di valutazione degli ++x e y++ non è specificato.Ma è perfettamente legale e istruzione valida.C'è no undefined behaviour in questa dichiarazione.Poiché le modifiche (++x e y++ sono fatti per distinti oggetti.

Ciò che rende la seguente dichiarazione

printf("%d %d\n", ++i, i++);

come comportamenti indefiniti è il fatto che queste due espressioni modificare il stesso oggetto i senza intervenire sequenza di punto.


Un altro dettaglio è che il virgola coinvolti nella chiamata printf() è un separatore, non l' operatore virgola.

Questa è una distinzione importante, perché il operatore virgola introdurre un sequenza di punto tra la valutazione della loro operandi, che rende il legale che segue:

int i = 5;
int j;

j = (++i, i++);  // No undefined behaviour here because the comma operator 
                 // introduces a sequence point between '++i' and 'i++'

printf("i=%d j=%d\n",i, j); // prints: i=7 j=6

L'operatore virgola valuta i suoi operandi da sinistra a destra e si ottiene il valore dell'ultimo operando.Quindi, in j = (++i, i++);, ++i incrementi i per 6 e i++ i rendimenti vecchio valore di i (6), che è assegnata j.Quindi i diventa 7 a causa di post-incremento.

Quindi, se il virgola la chiamata di funzione dovesse essere una virgola, un operatore di

printf("%d %d\n", ++i, i++);

non sarà un problema.Ma si richiama comportamenti indefiniti perché il virgola qui è un separatore.


Per coloro che sono nuovi al comportamenti indefiniti trarrebbero beneficio dalla lettura di Quello Che Ogni Programmatore C Dovrebbe Conoscere Il Comportamento Indefinito per capire il concetto e molte altre varianti di comportamenti indefiniti in C.

Questo post: Undefined, non specificato e definito dall'implementazione comportamento è anche rilevante.

Anche se è improbabile che qualsiasi compilatori e processori sarebbe in realtà lo fanno, sarebbe legale, sotto lo standard C, per il compilatore per implementare "i ++" con la sequenza:

In a single operation, read `i` and lock it to prevent access until further notice
Compute (1+read_value)
In a single operation, unlock `i` and store the computed value

Anche se non credo che nessun processori supportano l'hardware per permettere una cosa del genere ad essere svolto in modo efficiente, si può facilmente immaginare situazioni in cui tale comportamento sarebbe rendere il codice multi-threaded più facile (per esempio, sarebbe garantire che se due thread cercano di eseguire la sequenza sopra contemporaneamente, i otterrebbe incrementato di due) e non è del tutto inconcepibile che un futuro processore potrebbe fornire una caratteristica di una cosa del genere.

Se il compilatore dovesse scrivere i++ come sopra indicato (legale secondo lo standard) ed erano a intervallare le istruzioni di cui sopra per tutta la valutazione dell'espressione complessiva (anche legale), e se non è successo a notare che uno dei le altre istruzioni accaduto per accedere i, sarebbe possibile (e legale) per il compilatore di generare una sequenza di istruzioni che avrebbe stallo. A dire il vero, un compilatore quasi certamente rilevare il problema nel caso in cui lo stesso i variabile viene usata in entrambi i posti, ma se una routine accetta i riferimenti a due puntatori p e q, e usa (*p) e (*q) nella espressione di cui sopra (e non che usare i due volte) il compilatore non sarebbe necessario riconoscere o evitare lo stallo che si verificherebbe se l'indirizzo dell'oggetto stesso fosse passato sia p e q.

Lo standard C dice che una variabile deve essere assegnato solo al massimo una volta tra due punti di sequenza. Un semi-colon per esempio, è un punto di sequenza.
Così ogni affermazione della forma:

i = i++;
i = i++ + ++i;

e così via violare questa regola. Lo standard dice anche che il comportamento è indefinito e non specificato. Alcuni compilatori vengono rilevati da questi e producono qualche risultato, ma questo non è secondo la norma.

Tuttavia, due diverse variabili possono essere incrementati tra due punti di sequenza.

while(*src++ = *dst++);

Quanto sopra è una pratica comune, di codifica durante la copia / stringhe analizzando.

Mentre il sintassi di espressioni come a = a++ o a++ + a++ è legale, il comportamento di questi costrutti è undefined perché un sono in C standard non è obbedito. C99 6.5p2:

  1. Tra la precedente e la successiva sequenza di punto di un oggetto deve avere il suo valore memorizzato modificato più di una volta, la valutazione di un'espressione.[72] Inoltre, il valore precedente deve leggere solo per determinare il valore da memorizzare [73]

Con nota a piè di pagina 73 un ulteriore chiarimento che

  1. Questo paragrafo rende indefinito istruzione espressioni come

    i = ++i + 1;
    a[i++] = i;
    

    mentre permettendo

    i = i + 1;
    a[i] = i;
    

I vari sequenza di punti, elencati nell'Allegato C del C11 (e C99):

  1. I seguenti sono la sequenza dei punti descritti in 5.1.2.3:

    • Tra le valutazioni della funzione di designazione e di argomenti attuali in una funzione chiamata e la chiamata effettiva.(6.5.2.2).
    • Tra le valutazioni del primo e del secondo operandi dei seguenti operatori:logica E && (6.5.13);logico O || (6.5.14);virgola , (6.5.17).
    • Tra le valutazioni del primo operando della condizionale ?:operatore e qualunque sia il secondo e il terzo operandi è valutato (6.5.15).
    • La fine di una completa dichiarazione:declarators (6.7.6);
    • Tra la valutazione di un'espressione piena e completa successiva espressione da valutare.I seguenti sono pieni di espressioni:un inizializzatore che non è parte di un composto letterale (6.7.9);l'espressione in un'espressione di istruzione (6.8.3);l'controllare l'espressione di una istruzione di selezione (if o switch) (6.8.4);il controllo dell'espressione di un while o do istruzione (6.8.5);ciascuna delle (opzionale) espressione di una istruzione for (6.8.5.3);(opzionale) espressione in un ritorno economico (6.8.6.4).
    • Immediatamente prima di una funzione di libreria restituisce (7.1.4).
    • Dopo le azioni associate a ogni formattato funzione di ingresso/uscita specificatore di conversione (7.21.6, 7.29.2).
    • Immediatamente prima e immediatamente dopo ogni chiamata a una funzione di confronto, e anche tra ogni chiamata alla funzione di confronto e qualsiasi movimento di oggetti passati come argomenti alla chiamata (7.22.5).

La formulazione della stessa paragrafo C11 è:

  1. Se un effetto collaterale di un scalari oggetto non in sequenza relativa a un lato diverso effetto sulla stessa scalare un oggetto o un valore di calcolo utilizzando il valore della stessa scalare oggetto, il comportamento è indefinito.Se ci sono più ammissibili ordinamenti di sottoespressioni di un'espressione, il comportamento è indefinito se non in sequenza effetto collaterale si verifica in uno dei ordinamenti.84)

È possibile rilevare tali errori in un programma utilizzando ad esempio una recente versione di GCC con -Wall e -Werror, e quindi GCC sarà a titolo definitivo, si rifiutano di compilare il programma.Il seguente è l'output di gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005:

% gcc plusplus.c -Wall -Werror -pedantic
plusplus.c: In function ‘main’:
plusplus.c:6:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
    i = i++ + ++i;
    ~~^~~~~~~~~~~
plusplus.c:6:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
plusplus.c:10:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
    i = (i++);
    ~~^~~~~~~
plusplus.c:14:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
    u = u++ + ++u;
    ~~^~~~~~~~~~~
plusplus.c:14:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
plusplus.c:18:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
    u = (u++);
    ~~^~~~~~~
plusplus.c:22:6: error: operation on ‘v’ may be undefined [-Werror=sequence-point]
    v = v++ + ++v;
    ~~^~~~~~~~~~~
plusplus.c:22:6: error: operation on ‘v’ may be undefined [-Werror=sequence-point]
cc1: all warnings being treated as errors

L'importante è sapere quello che una sequenza punto è-e che cosa è una sequenza e che cosa non è.Per esempio il operatore virgola è una sequenza di punti, quindi

j = (i ++, ++ i);

è ben definito, e si incrementa i per uno, cedendo il vecchio valore, eliminare il valore;quindi all'operatore virgola, risolvere gli effetti collaterali;e poi incrementare i di uno, e il valore risultante diventa il valore dell'espressione - cioèquesto è solo un artificioso modo di scrivere j = (i += 2) che è ancora una volta una "intelligente" modo di scrivere

i += 2;
j = i;

Tuttavia, l' , in funzione di elenchi di argomenti è non un operatore virgola, e non vi è alcuna sequenza tra le valutazioni delle distinte argomentazioni;invece le loro valutazioni non sono in sequenza l'una rispetto all'altra;così la chiamata di funzione

int i = 0;
printf("%d %d\n", i++, ++i, i);

ha comportamenti indefiniti perché non vi è alcuna sequenza tra le valutazioni di i++ e ++i in funzione di argomenti, e il valore di i è quindi modificato due volte, sia i++ e ++i, tra la precedente e la successiva sequenza di punto.

https://stackoverflow.com/questions/29505280/incrementing-array-index- in-c qualcuno ha chiesto una dichiarazione come:

int k[] = {0,1,2,3,4,5,6,7,8,9,10};
int i = 0;
int num;
num = k[++i+k[++i]] + k[++i];
printf("%d", num);

che stampa 7 ... il PO prevede per stampare 6.

Gli incrementi ++i non sono garantiti per tutti completi prima che il resto dei calcoli. In effetti, diversi compilatori otterranno risultati diversi qui. Nell'esempio che hai fornito, il primo 2 ++i eseguito, quindi sono stati letti i valori di k[], poi l'ultimo ++i poi k[].

num = k[i+1]+k[i+2] + k[i+3];
i += 3

compilatori moderni ottimizzeranno molto bene. In realtà, forse meglio di quanto il codice è scritto in origine (supponendo che aveva funzionato il modo in cui aveva sperato).

La tua domanda è stata probabilmente no", Perché sono questi costrutti comportamento indefinito in C?".La tua domanda è stata probabilmente "Perché questo codice ++) non mi danno il valore che mi aspettavo?", e qualcuno ha segnato la tua domanda come un duplicato, e ha inviato qui.

Questo risposta prova a rispondere a questa domanda:perché il tuo codice non dare la risposta che si aspettava, e come si può imparare a riconoscere (ed evitare) espressioni che non funziona come dovrebbe.

Suppongo che hai sentito la definizione di base di C ++ e -- gli operatori ora, e come la forma di prefisso ++x differisce dalla forma di postfix x++.Ma questi operatori sono difficile pensare, quindi, per assicurarsi di aver capito, forse hai scritto un piccolo programma di test che coinvolgono qualcosa di simile

int x = 5;
printf("%d %d %d\n", x, ++x, x++);

Ma, con grande sorpresa, questo programma ha fatto non aiutare a capire -- è stampata una strana, inattesa, inspiegabile uscita, suggerendo che forse ++ fa qualcosa di completamente diverso, non è affatto quello che si pensava che non l'ha fatto.

O, forse siete alla ricerca di un hard-per-capire espressioni come

int x = 5;
x = x++ + ++x;
printf("%d\n", x);

Forse qualcuno ti ha dato il codice, come un puzzle.Questo codice non ha senso, soprattutto se si esegue -- e se si compila e si esegue sotto due diversi compilatori, è probabile che per ottenere due risposte diverse!Cosa c'è che non va?La risposta corretta?(E la risposta è che entrambi sono, o non lo sono.)

Come avete sentito da ora, tutte queste espressioni sono undefined, il che significa che il linguaggio C fornisce alcuna garanzia su ciò che possono fare.Questa è una strana e sorprendente risultato, perché probabilmente avete pensato che qualsiasi programma che si potrebbe scrivere, purché compilato ed eseguito, avrebbe generato un unico e ben definito di uscita.Ma nel caso di un comportamento indefinito, che non è così.

Ciò che rende un'espressione indefinita?Sono espressioni che coinvolgono ++ e -- sempre undefined?Naturalmente non:questi sono utili per gli operatori, e se si utilizzano correttamente, essi sono perfettamente ben definito.

Per le espressioni stiamo parlando di quello che li rende indefinito è quando non c'è troppo da fare in una sola volta, quando non siamo sicuri di quale ordine le cose avvenire, ma quando l'ordine è importante per il risultato che si ottiene.

Torniamo ai due esempi che ho utilizzato in questa risposta.Quando ho scritto

printf("%d %d %d\n", x, ++x, x++);

la domanda è, prima di chiamare printf, fa il compilatore a calcolare il valore di x prima, o x++, o forse ++x?Ma si scopre non sappiamo.Non c'è una regola in C che dice che gli argomenti di una funzione vengono valutate da sinistra a destra o da destra a sinistra, o in qualche altro ordine.Quindi non si può dire se il compilatore non x prima, quindi ++x, poi x++, o x++ quindi ++x quindi x, o qualche altro ordine.Ma l'ordine chiaramente le cose, in quanto a seconda dell'ordine, il compilatore utilizza, vedremo chiaramente ottenere risultati diversi stampato da printf.

Che dire di questo pazzo espressione?

x = x++ + ++x;

Il problema con questa espressione è che contiene tre diversi tentativi di modificare il valore di x:(1) il x++ parte cerca di aggiungere da 1 a x, memorizzare il nuovo valore nel x, e di restituire il vecchio valore di x;(2) il ++x parte cerca di aggiungere da 1 a x, memorizzare il nuovo valore nel x, e restituisce il nuovo valore di x;e (3) il x = parte tenta di assegnare la somma di altri due x.Quali di questi tre tentativi di assegnazioni di "vincere"?Quale dei tre valori effettivamente assegnata x?Di nuovo, e forse sorprendentemente, non c'è una regola in C da dire.

Si potrebbe immaginare che di precedenza o di associatività a sinistra o a destra di valutazione vi dirà che ordine di cose avvenire, ma non è così.Non si può credere a me, ma vi prego di prendere la mia parola per esso, e lo dirò di nuovo:la precedenza e associatività non determinare ogni aspetto dell'ordine di valutazione di un'espressione in C.In particolare, se all'interno di una espressione ci sono diversi punti in cui si tenta di assegnare un nuovo valore a qualcosa di simile x, di precedenza e associatività fare non ci dicono che di quei tentativi che accade per primo, o l'ultimo o il nulla.


Quindi, con tutto ciò che di sfondo e l'introduzione di mezzo, se si desidera assicurarsi che tutti i programmi sono ben definiti, quali espressioni si può scrivere, e che quelli che non si possono scrivere?

Queste espressioni sono tutte belle:

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 indefinito:

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 si può dire che le espressioni sono ben definiti, e le espressioni che non sono?

Come ho detto in precedenza, le espressioni non definite sono quelle in cui c'è troppo da fare in una sola volta, in cui non è possibile essere sicuri di quale ordine di cose, e dove l'ordine di questioni:

  1. Se c'è una variabile che diventa sempre più modificato (assegnata) in due o più luoghi diversi, come fai a sapere che la modifica avviene prima?
  2. Se c'è una variabile che diventa sempre più modificato in un unico luogo, e avendo il suo valore utilizzato in un altro luogo, come si fa a sapere se si utilizza il valore vecchio o il nuovo valore?

Come esempio di #1, nell'espressione

x = x++ + ++x;

ci sono tre tentativi per modificare `x.

Come esempio di #2, nell'espressione

y = x + x++;

entrambi usiamo il valore di x, e modificarlo.

Ecco la risposta:assicurarsi che qualsiasi espressione di scrivere, ogni variabile viene modificato più di una volta, e se una variabile viene modificato, non è il tentativo di utilizzare il valore della variabile da qualche altra parte.

Una buona spiegazione di ciò che accade in questo tipo di calcolo è fornita nel documento n1188 da sito ISO W14 .

spiego le idee.

La regola principale della norma ISO 9899 che si applica in questa situazione è 6.5p2.

  

Tra il precedente e successivo punto sequenza un oggetto deve avere il suo valore memorizzato modificato al più una volta dalla valutazione di un'espressione. Inoltre, il valore precedente deve intendersi solo per determinare il valore da memorizzare.

I punti di sequenza in un'espressione come i=i++ sono prima e dopo i= i++.

Nel documento che ho citato sopra è spiegato che si può capire il programma come formate da piccole scatole, ogni scatola contenente le istruzioni tra 2 punti di sequenza consecutivi. I punti di sequenza sono definiti nell'allegato C della norma, nel caso di i=i++ ci sono 2 punti di sequenza che delimitano un full-espressione. Tale espressione è sintatticamente equivalente a una voce di expression-statement nella forma Backus-Naur della grammatica (una grammatica è fornita in allegato A della normativa).

Quindi, l'ordine delle istruzioni all'interno di una scatola non ha alcun ordine chiaro.

i=i++

può essere interpretato come

tmp = i
i=i+1
i = tmp

o come

tmp = i
i = tmp
i=i+1

perché entrambe tutte queste forme di interpretare il codice i=i++ sono validi e perché entrambi generano risposte diverse, il comportamento è indefinito.

Quindi, un punto di sequenza può essere visto da l'inizio e la fine di ogni casella che compone il programma [le caselle sono unità atomiche in C] e all'interno di una scatola l'ordine delle istruzioni non è definito in tutti i casi. La modifica che l'ordine si può cambiare il risultato a volte.

EDIT:

Altro buona fonte per spiegare tali ambiguità sono le voci da sito c-faq (Pubblicato anche come un libro ), vale a dire qui e qui e qui .

Il motivo è che il programma è in esecuzione un comportamento indefinito. Il problema è l'ordine di valutazione, perché non v'è alcun punto sequenza desiderata secondo C ++ 98 standard (nessuna operazione viene sequenziato prima o dopo l'altro secondo la terminologia C ++ 11).

Tuttavia, se si tiene fede ad un compilatore, si trova il comportamento persistente, a patto che non si aggiunge chiamate di funzione o puntatori, che renderebbe il comportamento più disordinato.

  • Quindi, prima del GCC: Utilizzando Nuwen MinGW 15 GCC 7.1 si otterrà:

    #include<stdio.h>
    int main(int argc, char ** argv)
    {
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i); // 2
    
    i = 1;
    i = (i++);
    printf("%d\n", i); //1
    
    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u); // 2
    
    u = 1;
    u = (u++);
    printf("%d\n", u); //1
    
    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v); //2
    

    }

Come funziona GCC funziona? essa valuta le espressioni sub a sinistra a destra per il lato destro (RHS), quindi assegna il valore sul lato sinistro (LHS). Questo è esattamente come Java e C # si comportano e definire i loro standard. (Sì, il software equivalente in Java e C # ha definito i comportamenti). Si valuta ogni sotto espressione uno per uno in comunicazione RHS in ordine da sinistra a destra; per ogni espressione sub: il ++ c (pre-incremento) viene valutato per primo il valore c è utilizzato per l'operazione, poi il palo incremento c ++)

.

GCC C ++: Operatori

  

In GCC C ++, la precedenza degli operatori controlla l'ordine   che i singoli operatori vengono valutati

il codice equivalente definito comportamento C ++ come capisce GCC:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    //i = i++ + ++i;
    int r;
    r=i;
    i++;
    ++i;
    r+=i;
    i=r;
    printf("%d\n", i); // 2

    i = 1;
    //i = (i++);
    r=i;
    i++;
    i=r;
    printf("%d\n", i); // 1

    volatile int u = 0;
    //u = u++ + ++u;
    r=u;
    u++;
    ++u;
    r+=u;
    u=r;
    printf("%d\n", u); // 2

    u = 1;
    //u = (u++);
    r=u;
    u++;
    u=r;
    printf("%d\n", u); // 1

    register int v = 0;
    //v = v++ + ++v;
    r=v;
    v++;
    ++v;
    r+=v;
    v=r;
    printf("%d\n", v); //2
}

Poi andiamo a Visual Studio . Visual Studio 2015, si ottiene:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i); // 3

    i = 1;
    i = (i++);
    printf("%d\n", i); // 2 

    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u); // 3

    u = 1;
    u = (u++);
    printf("%d\n", u); // 2 

    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v); // 3 
}

Come funziona Visual Studio, ci vuole un altro approccio, valuta tutti i pre-incrementi espressioni primo passaggio, poi utilizza i valori delle variabili nelle operazioni di secondo passaggio, assegnare da RHS a LHS al terzo passaggio, poi finalmente passarlo valuta tutte le espressioni di post-incremento in un solo passaggio.

Quindi, l'equivalente in definito comportamento C ++ come Visual C ++ comprende:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int r;
    int i = 0;
    //i = i++ + ++i;
    ++i;
    r = i + i;
    i = r;
    i++;
    printf("%d\n", i); // 3

    i = 1;
    //i = (i++);
    r = i;
    i = r;
    i++;
    printf("%d\n", i); // 2 

    volatile int u = 0;
    //u = u++ + ++u;
    ++u;
    r = u + u;
    u = r;
    u++;
    printf("%d\n", u); // 3

    u = 1;
    //u = (u++);
    r = u;
    u = r;
    u++;
    printf("%d\n", u); // 2 

    register int v = 0;
    //v = v++ + ++v;
    ++v;
    r = v + v;
    v = r;
    v++;
    printf("%d\n", v); // 3 
}

Visual Studio stati di documentazione a Precedenza e Ordine di valutazione :

  

Se più operatori appaiono insieme, hanno priorità equivalente e vengono valutati in base alla loro associatività. Gli operatori nella tabella sono descritti nelle sezioni che iniziano con Postfix operatori.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top