Comportamento indefinito e punti sequenza


987

Cosa sono i "punti sequenza"?

Qual è la relazione tra comportamento indefinito e punti sequenza?

Uso spesso espressioni divertenti e contorte come a[++i] = i;, per farmi sentire meglio. Perché dovrei smettere di usarli?

Se hai letto questo, assicurati di visitare la domanda di follow-up Comportamento indefinito e punti sequenza ricaricati .

(Nota: questo dovrebbe essere una voce alle FAQ C ++ di Stack Overflow . Se vuoi criticare l'idea di fornire una FAQ in questo modulo, allora la pubblicazione su meta che ha iniziato tutto questo sarebbe il posto dove farlo. tale domanda viene monitorata nella chatroom di C ++ , dove l'idea FAQ è iniziata in primo luogo, quindi è molto probabile che la tua risposta venga letta da coloro che hanno avuto l'idea.)

Risposte:


683

C ++ 98 e C ++ 03

Questa risposta è per le versioni precedenti dello standard C ++. Le versioni C ++ 11 e C ++ 14 dello standard non contengono formalmente "punti di sequenza"; le operazioni sono invece "sequenziate prima" o "non sequenziali" o "sequenziate in modo indeterminato". L'effetto netto è essenzialmente lo stesso, ma la terminologia è diversa.


Disclaimer : Ok. Questa risposta è un po 'lunga. Quindi abbi pazienza durante la lettura. Se conosci già queste cose, leggerle di nuovo non ti farà impazzire.

Prerequisiti : una conoscenza elementare dello standard C ++


Cosa sono i punti sequenza?

Lo standard dice

In alcuni punti specifici della sequenza di esecuzione denominati punti di sequenza , tutti gli effetti collaterali delle valutazioni precedenti devono essere completi e non devono aver avuto luogo effetti collaterali delle valutazioni successive. (§1.9 / 7)

Effetti collaterali? Quali sono gli effetti collaterali?

La valutazione di un'espressione produce qualcosa e se in aggiunta c'è un cambiamento nello stato dell'ambiente di esecuzione si dice che l'espressione (la sua valutazione) ha alcuni effetti collaterali.

Per esempio:

int x = y++; //where y is also an int

Oltre all'operazione di inizializzazione, il valore di yviene modificato a causa dell'effetto collaterale ++dell'operatore.

Fin qui tutto bene. Passando ai punti di sequenza. Una definizione di alternanza di punti seq fornita dall'autore comp.lang.c Steve Summit:

Il punto di sequenza è un punto nel tempo in cui la polvere si è depositata e tutti gli effetti collaterali visti finora sono garantiti per essere completi.


Quali sono i punti di sequenza comuni elencati nello standard C ++?

Quelli sono:

  • al termine della valutazione dell'espressione completa ( §1.9/16) (Un'espressione completa è un'espressione che non è una sottoespressione di un'altra espressione.) 1

    Esempio :

    int a = 5; // ; is a sequence point here
  • nella valutazione di ciascuna delle seguenti espressioni dopo la valutazione della prima espressione ( §1.9/18) 2

    • a && b (§5.14)
    • a || b (§5.15)
    • a ? b : c (§5.16)
    • a , b (§5.18)(qui a, b è un operatore virgola; in func(a,a++) ,non è un operatore virgola, è semplicemente un separatore tra gli argomenti ae a++. Quindi il comportamento non è definito in quel caso (se aè considerato un tipo primitivo))
  • a una chiamata di funzione (indipendentemente dal fatto che la funzione sia in linea), dopo la valutazione di tutti gli argomenti della funzione (se presenti) che ha luogo prima dell'esecuzione di qualsiasi espressione o istruzione nel corpo della funzione ( §1.9/17).

1: Nota: la valutazione di un'espressione completa può includere la valutazione di sottoespressioni che non fanno parte lessicale dell'espressione completa. Ad esempio, le sottoespressioni coinvolte nella valutazione delle espressioni di argomenti predefinite (8.3.6) sono considerate create nell'espressione che chiama la funzione, non nell'espressione che definisce l'argomento predefinito

2: Gli operatori indicati sono gli operatori integrati, come descritto nella clausola 5. Quando uno di questi operatori è sovraccarico (clausola 13) in un contesto valido, designando così una funzione operatore definita dall'utente, l'espressione designa una chiamata di funzione e gli operandi formano un elenco di argomenti, senza un punto di sequenza implicito tra di loro.


Che cos'è il comportamento indefinito?

Lo standard definisce il comportamento indefinito nella sezione §1.3.12come

comportamenti, come quelli che potrebbero derivare dall'uso di un costrutto di programma errato o di dati errati, per i quali la presente norma internazionale non impone requisiti 3 .

Un comportamento indefinito può anche essere previsto quando questo standard internazionale omette la descrizione di qualsiasi definizione esplicita di comportamento.

3: il comportamento indefinito consentito va dall'ignorare completamente la situazione con risultati imprevedibili, al comportamento durante la traduzione o l'esecuzione del programma in un modo documentato caratteristico dell'ambiente (con o senza l'emissione di un messaggio diagnostico), alla conclusione di una traduzione o esecuzione (con l'emissione di un messaggio diagnostico).

In breve, un comportamento indefinito significa che può succedere di tutto, dai demoni che volano fuori dal tuo naso alla tua ragazza che rimane incinta.


Qual è la relazione tra comportamento indefinito e punti sequenza?

Prima di entrare nel merito, è necessario conoscere le differenze tra comportamento indefinito, comportamento non specificato e comportamento definito dall'implementazione .

Devi anche saperlo the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.

Per esempio:

int x = 5, y = 6;

int z = x++ + y++; //it is unspecified whether x++ or y++ will be evaluated first.

Un altro esempio qui .


Ora lo standard in §5/4dice

  • 1) Tra il punto di sequenza precedente e quello successivo, un oggetto scalare deve avere il valore memorizzato modificato al massimo una volta dalla valutazione di un'espressione.

Cosa significa?

Informalmente significa che tra due punti di sequenza una variabile non deve essere modificata più di una volta. In un'istruzione expression, di next sequence pointsolito si trova al punto e virgola finale e previous sequence pointè alla fine dell'istruzione precedente. Un'espressione può anche contenere intermedio sequence points.

Dalla frase precedente le seguenti espressioni invocano il comportamento indefinito:

i++ * ++i;   // UB, i is modified more than once btw two SPs
i = ++i;     // UB, same as above
++i = 2;     // UB, same as above
i = ++i + 1; // UB, same as above
++++++i;     // UB, parsed as (++(++(++i)))

i = (i, ++i, ++i); // UB, there's no SP between `++i` (right most) and assignment to `i` (`i` is modified more than once btw two SPs)

Ma le seguenti espressioni vanno bene:

i = (i, ++i, 1) + 1; // well defined (AFAIK)
i = (++i, i++, i);   // well defined 
int j = i;
j = (++i, i++, j*i); // well defined

  • 2) Inoltre, è possibile accedere al valore precedente solo per determinare il valore da memorizzare.

Cosa significa? Significa che se un oggetto viene scritto all'interno di un'espressione completa, tutti gli accessi ad esso all'interno della stessa espressione devono essere direttamente coinvolti nel calcolo del valore da scrivere .

Ad esempio in i = i + 1tutto l'accesso di i(in LHS e in RHS) sono direttamente coinvolti nel calcolo del valore da scrivere. Quindi va bene

Questa regola limita effettivamente le espressioni legali a quelle in cui gli accessi precedono in modo dimostrabile la modifica.

Esempio 1:

std::printf("%d %d", i,++i); // invokes Undefined Behaviour because of Rule no 2

Esempio 2:

a[i] = i++ // or a[++i] = i or a[i++] = ++i etc

è vietato perché uno degli accessi di i(quello in a[i]) non ha nulla a che fare con il valore che finisce per essere memorizzato in i (che accade sopra in i++), e quindi non c'è un buon modo per definire - né per la nostra comprensione o il compilatore - se l'accesso deve avvenire prima o dopo la memorizzazione del valore incrementato. Quindi il comportamento non è definito.

Esempio 3:

int x = i + i++ ;// Similar to above

Seguire la risposta per C ++ 11 qui .


45
*p++ = 4 non è un comportamento indefinito. *p++è interpretato come *(p++). p++restituisce p(una copia) e il valore viene memorizzato all'indirizzo precedente. Perché ciò invocherebbe UB? Va benissimo.
Prasoon Saurav,

7
@Mike: AFAIK, non ci sono copie (legali) dello standard C ++ a cui puoi collegarti.
sabato

11
Bene, allora potresti avere un link alla pagina dell'ordine pertinente ISO. Ad ogni modo, pensandoci, la frase "conoscenza elementare dello standard C ++" sembra un po 'una contraddizione in termini, dal momento che se stai leggendo lo standard, hai superato il livello elementare. Forse potremmo elencare quali cose nella lingua hai bisogno di una comprensione di base, come la sintassi delle espressioni, l'ordine delle operazioni e forse il sovraccarico dell'operatore?
Mike DeSimone,

41
Non sono sicuro che citare lo standard sia il modo migliore per insegnare ai neofiti
Inverso

6
@Adrian La prima espressione richiama un UB perché non c'è un punto di sequenza tra l'ultimo ++ie l'assegnazione a i. La seconda espressione non invoca UB perché espressione inon modifica il valore di i. Nel secondo esempio i++viene seguito da un punto sequenza ( ,) prima che venga chiamato l'operatore di assegnazione.
Kolyunya,

276

Questo è un seguito alla mia risposta precedente e contiene materiale correlato a C ++ 11. .


Prerequisiti : una conoscenza elementare delle relazioni (matematica).


È vero che non ci sono punti di sequenza in C ++ 11?

Sì! Questo è molto vero

Punti di sequenza sono stati sostituiti da Sequenced Prima e Sequenced dopo (e non in sequenza e indeterminatamente Sequenced ) le relazioni in C ++ 11.


Che cosa è esattamente questa cosa "in sequenza prima"?

Sequenced Before (§1.9 / 13) è una relazione che è:

tra le valutazioni eseguite da un singolo thread e induce un rigoroso ordine parziale 1

Formalmente significa date due valutazioni (vedi sotto) A e B, se Aè sequenziato prima B , l'esecuzione di A deve precedere l'esecuzione di B. Se Anon è in sequenza prima Be Bnon è in sequenza prima A, allora Ae non Bsono seguiti 2 .

Le valutazioni Ae Bsono indeterminate in sequenza quando una delle due Aè prima Bo Bprima A, ma non è specificato quale 3 .

[NOTE]
1: Un ordine parziale stretto è una relazione binaria "<" su un insieme Pche è asymmetric, e transitive, cioè, per tutti a, be cin P, abbiamo che:
........ (i). se a <b allora ¬ (b <a) ( asymmetry);
........ (ii). se a <b e b <c quindi a <c ( transitivity).
2: l'esecuzione di valutazioni senza conseguenze può sovrapporsi .
3: le valutazioni in sequenza indeterminata non possono sovrapporsi , ma entrambe possono essere eseguite per prime.


Qual è il significato della parola "valutazione" nel contesto di C ++ 11?

In C ++ 11, la valutazione di un'espressione (o di una sottoespressione) in generale include:

  • calcoli di valore (inclusa la determinazione dell'identità di un oggetto per la valutazione del valore e il recupero di un valore precedentemente assegnato a un oggetto per la valutazione del valore ) e

  • innesco di effetti collaterali .

Ora (§1.9 / 14) dice:

Ogni calcolo di valore ed effetto collaterale associato a un'espressione completa viene sequenziato prima di ogni calcolo di valore ed effetto collaterale associato alla successiva espressione completa da valutare .

  • Esempio di prova:

    int x; x = 10; ++x;

    Il calcolo del valore e l'effetto collaterale associato ++xsono in sequenza dopo il calcolo del valore e l'effetto collaterale dix = 10;


Quindi ci deve essere una relazione tra il comportamento indefinito e le cose sopra menzionate, giusto?

Sì! Giusto.

In (§1.9 / 15) è stato menzionato questo

Salvo dove diversamente indicato, le valutazioni degli operandi dei singoli operatori e delle sottoespressioni delle singole espressioni non sono seguite 4 .

Per esempio :

int main()
{
     int num = 19 ;
     num = (num << 3) + (num >> 3);
} 
  1. La valutazione degli operandi +dell'operatore non è seguita l'una rispetto all'altra.
  2. La valutazione degli operandi <<e degli >>operatori non è seguita l'una rispetto all'altra.

4: In un'espressione che viene valutata più di una volta durante l'esecuzione di un programma, non è necessario che le valutazioni senza sequenze e sequenze indeterminate delle sue sottoespressioni siano eseguite in modo coerente in diverse valutazioni.

(§1.9 / 15) I calcoli del valore degli operandi di un operatore sono sequenziati prima del calcolo del valore del risultato dell'operatore.

Ciò significa che nel x + ycalcolo del valore di xe ysono in sequenza prima del calcolo del valore di (x + y).

Ma ancora più importante

(§1.9 / 15) Se un effetto collaterale su un oggetto scalare non è seguito rispetto a nessuno dei due

(a) un altro effetto collaterale sullo stesso oggetto scalare

o

(b) un calcolo del valore usando il valore dello stesso oggetto scalare.

il comportamento non è definito .

Esempi:

int i = 5, v[10] = { };
void  f(int,  int);
  1. i = i++ * ++i; // Undefined Behaviour
  2. i = ++i + i++; // Undefined Behaviour
  3. i = ++i + ++i; // Undefined Behaviour
  4. i = v[i++]; // Undefined Behaviour
  5. i = v[++i]: // Well-defined Behavior
  6. i = i++ + 1; // Undefined Behaviour
  7. i = ++i + 1; // Well-defined Behaviour
  8. ++++i; // Well-defined Behaviour
  9. f(i = -1, i = -1); // Undefined Behaviour (see below)

Quando si chiama una funzione (indipendentemente dal fatto che la funzione sia in linea), ogni calcolo del valore ed effetto collaterale associato a qualsiasi espressione di argomento o all'espressione postfissa che designa la funzione chiamata, viene sequenziato prima dell'esecuzione di ogni espressione o istruzione nel corpo del chiamato funzione. [ Nota: i calcoli di valore e gli effetti collaterali associati a diverse espressioni di argomenti non hanno conseguenze . - nota finale ]

Espressioni (5), (7)e (8)non invocano un comportamento indefinito. Controlla le seguenti risposte per una spiegazione più dettagliata.


Nota finale :

Se trovi qualche difetto nel post, lascia un commento. Utenti esperti (con rappresentante> 20000) non esitate a modificare il post per correggere errori di battitura e altri errori.


3
Invece di "asimmetrici", sequenziali prima / dopo sono relazioni "antisimmetriche". Questo dovrebbe essere modificato nel testo per conformarsi alla definizione di un ordine parziale dato in seguito (che concorda anche con Wikipedia).
TemplateRex,

1
Perché 7) nell'ultimo esempio è un UB? Forse dovrebbe essere f(i = -1, i = 1)?
Mikhail,

1
Ho corretto la descrizione della relazione "in sequenza prima". È un ordine parziale rigoroso . Ovviamente, un'espressione non può essere sequenziata prima di se stessa, quindi la relazione non può essere riflessiva. Quindi è asimmetrico non antisimmetrico.
ThomasMcLeod,

1
5) essere ben definito mi ha lasciato senza parole. la spiegazione di Johannes Schaub non era del tutto semplice da ottenere. Soprattutto perché credevo che anche in ++i(essendo il valore valutato prima +dell'operatore che lo sta usando), lo standard non dice ancora che il suo effetto collaterale debba essere finito. Ma in effetti, poiché restituisce un riferimento a un lvalueche è esso istesso, DEVE aver terminato l'effetto collaterale poiché la valutazione deve essere terminata, quindi il valore deve essere aggiornato. Questa è stata la parte folle da ottenere in effetti.
v

"I membri del comitato ISO C ++ hanno ritenuto che le cose relative ai punti di sequenza fossero piuttosto difficili da capire. Così hanno deciso di sostituirlo con le relazioni sopra menzionate solo per una formulazione più chiara e una maggiore precisione." - hai un riferimento per tale richiesta? Mi sembra che le nuove relazioni siano più difficili da capire.
MM

30

C ++ 17 ( N4659) include una proposta Ordine di valutazione delle espressioni di perfezionamento per C ++ idiomatico che definisce un ordine di valutazione delle espressioni più rigoroso.

In particolare, la seguente frase

8.18 Operatori di assegnazione e assegnazione composti :
....

In tutti i casi, l'assegnazione viene eseguita in sequenza dopo il calcolo del valore degli operandi destro e sinistro e prima del calcolo del valore dell'espressione di assegnazione. L'operando di destra è sequenziato prima dell'operando di sinistra.

insieme al seguente chiarimento

Un'espressione X è detto di essere sequenziato prima un'espressione Y se ogni valore computazione e ogni effetto collaterale associato con l'espressione X viene sequenza prima di ogni calcolo valore ed ogni effetto collaterale associato con l'espressione Y .

rendere validi diversi casi di comportamento precedentemente non definito, incluso quello in questione:

a[++i] = i;

Tuttavia, molti altri casi simili portano ancora a comportamenti indefiniti.

In N4140:

i = i++ + 1; // the behavior is undefined

Ma in N4659

i = i++ + 1; // the value of i is incremented
i = i++ + i; // the behavior is undefined

Naturalmente, l'uso di un compilatore conforme a C ++ 17 non significa necessariamente che si dovrebbe iniziare a scrivere tali espressioni.


perché i = i++ + 1;viene definito il comportamento in c ++ 17, penso che anche se "L'operando di destra è sequenziato prima dell'operando di sinistra", tuttavia la modifica di "i ++" e l'effetto collaterale per l'assegnazione non sono conseguiti, per favore fornisci maggiori dettagli per interpretarli
jack X

@jackX ho esteso la risposta :).
AlexD

sì, penso che il dettaglio dell'interpretazione della frase "L'operando di destra è in sequenza prima dell'operando di sinistra" è più utile. In quanto "L'operando di destra è in sequenza prima dell'operando di sinistra" significa che il calcolo del valore e l'effetto collaterale associato all'operando di destra sono sequenziato prima di quello dell'operando di sinistra. come hai fatto :-)
jack X

11

Immagino che ci sia una ragione fondamentale per il cambiamento, non è solo estetico rendere più chiara la vecchia interpretazione: quella ragione è concorrenza. L'ordine di elaborazione non specificato è semplicemente la selezione di uno dei numerosi ordini seriali possibili, questo è abbastanza diverso dagli ordini prima e dopo, perché se non esiste un ordine specificato, è possibile una valutazione simultanea: non così con le vecchie regole. Ad esempio in:

f (a,b)

precedentemente o poi b, oppure b poi a. Ora, aeb possono essere valutati con istruzioni interfogliate o anche su core diversi.


5
Ritengo, tuttavia, che se "a" o "b" includono una chiamata di funzione, sono indeterminati in sequenza piuttosto che non di conseguenza, il che significa che tutti gli effetti collaterali di uno devono verificarsi prima di qualsiasi effetto collaterale altro, sebbene il compilatore non debba essere coerente su quale dei due passi per primo. Se ciò non fosse più vero, si spezzerebbe molto codice che si basa sulle operazioni che non si sovrappongono (ad esempio se 'a' e 'b' ciascuna imposta, usa e toglie, uno stato statico condiviso).
Supercat,

2

In C99(ISO/IEC 9899:TC3)cui sembra assente da questa discussione finora i seguenti steteents sono fatti riguardo all'ordine di valutazione.

[...] l'ordine di valutazione delle sottoespressioni e l'ordine in cui si verificano gli effetti collaterali sono entrambi non specificati. (Sezione 6.5 pp 67)

L'ordine di valutazione degli operandi non è specificato. Se si tenta di modificare il risultato di un operatore di assegnazione o di accedervi dopo il successivo punto di sequenza, il comportamento [sic] non è definito (Sezione 6.5.16 pagg. 91)


2
La domanda è taggata C ++ e non C, il che è positivo perché il comportamento in C ++ 17 è abbastanza diverso dal comportamento nelle versioni precedenti - e non ha alcuna relazione con il comportamento in C11, C99, C90, ecc. O ha pochissimo relazione con esso. Nel complesso, suggerirei di rimuoverlo. Più significativamente, dobbiamo trovare domande e risposte equivalenti per C e assicurarci che sia OK (e nota che C ++ 17, in particolare, cambia le regole - il comportamento in C ++ 11 e precedenti era più o meno lo stesso di in C11, anche se la verbosità che lo descrive in C usa ancora "punti di sequenza" mentre C ++ 11 e versioni successive non lo fanno.
Jonathan Leffler
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.