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 y
viene 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 a
e 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.12
come
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/4
dice
- 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 point
solito 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 + 1
tutto 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 .
*p++ = 4
non è un comportamento indefinito.*p++
è interpretato come*(p++)
.p++
restituiscep
(una copia) e il valore viene memorizzato all'indirizzo precedente. Perché ciò invocherebbe UB? Va benissimo.