Stavo leggendo sull'ordine delle violazioni della valutazione e mi danno un esempio che mi confonde.
1) Se un effetto collaterale su un oggetto scalare non è sequenziato rispetto ad un altro effetto collaterale sullo stesso oggetto scalare, il comportamento non è definito.
// snip f(i = -1, i = -1); // undefined behavior
In questo contesto, i
è un oggetto scalare , che apparentemente significa
I tipi aritmetici (3.9.1), i tipi di enumerazione, i tipi di puntatore, i tipi di puntatore a membro (3.9.2), le versioni std :: nullptr_t e cv qualificati di questi tipi (3.9.3) sono chiamati collettivamente tipi scalari.
Non vedo come la dichiarazione sia ambigua in quel caso. Mi sembra che, indipendentemente dal fatto che il primo o il secondo argomento vengano valutati per primi, i
finisce come -1
, e lo sono anche entrambi gli argomenti -1
.
Qualcuno può chiarire per favore?
AGGIORNARE
Apprezzo molto tutta la discussione. Finora, mi piace molto la risposta di @ harmic poiché espone le insidie e le complessità della definizione di questa affermazione, nonostante quanto a prima vista appaia semplice. @ acheong87 sottolinea alcuni problemi che emergono quando si usano i riferimenti, ma penso che sia ortogonale all'aspetto senza effetti collaterali di questa domanda.
SOMMARIO
Dal momento che questa domanda ha ricevuto molta attenzione, riassumerò i principali punti / risposte. In primo luogo, consentitemi una piccola digressione per sottolineare che "perché" può avere significati strettamente correlati ma sottilmente diversi, vale a dire "per quale causa ", "per quale motivo " e "per quale scopo ". Raggrupperò le risposte per quale di questi significati di "perché" hanno affrontato.
per quale causa
La risposta principale qui viene da Paul Draper , con Martin J che contribuisce con una risposta simile ma non così ampia. La risposta di Paul Draper si riduce a
È un comportamento indefinito perché non è definito quale sia il comportamento.
La risposta è nel complesso molto buona in termini di spiegazione di ciò che dice lo standard C ++. Affronta anche alcuni casi correlati di UB come f(++i, ++i);
e f(i=1, i=-1);
. Nel primo dei casi correlati, non è chiaro se il primo argomento debba essere i+1
e il secondo i+2
o viceversa; nel secondo, non è chiaro se i
dovrebbe essere 1 o -1 dopo la chiamata di funzione. Entrambi questi casi sono UB perché rientrano nella seguente regola:
Se un effetto collaterale su un oggetto scalare non è seguito rispetto ad un altro effetto collaterale sullo stesso oggetto scalare, il comportamento non è definito.
Pertanto, f(i=-1, i=-1)
è anche UB poiché rientra nella stessa regola, nonostante l'intenzione del programmatore sia (IMHO) ovvia e inequivocabile.
Paul Draper lo rende anche esplicito nelle sue conclusioni
Potrebbe essere stato definito un comportamento? Sì. È stato definito? No.
che ci porta alla domanda "per quale motivo / scopo è stato f(i=-1, i=-1)
lasciato come comportamento indefinito?"
per quale motivo / scopo
Sebbene ci siano alcune sviste (forse trascurate) nello standard C ++, molte omissioni sono ben ragionate e servono a uno scopo specifico. Sebbene sia consapevole che lo scopo è spesso "rendere più facile il lavoro dello scrittore del compilatore" o "codice più veloce", ero principalmente interessato a sapere se c'è una buona ragione per lasciare f(i=-1, i=-1)
UB.
harmic e supercat forniscono le risposte principali che forniscono un motivo per l'UB. Harmic sottolinea che un compilatore ottimizzante che potrebbe spezzare le apparentemente atomiche operazioni di assegnazione in più istruzioni della macchina e che potrebbe ulteriormente intercalare quelle istruzioni per una velocità ottimale. Questo potrebbe portare ad alcuni risultati molto sorprendenti: i
finisce come -2 nel suo scenario! Pertanto, harmic dimostra come l'assegnazione dello stesso valore a una variabile più di una volta può avere effetti negativi se le operazioni non sono seguite.
supercat fornisce un'esposizione correlata delle insidie del tentativo di arrivare f(i=-1, i=-1)
a fare quello che sembra che dovrebbe fare. Sottolinea che su alcune architetture ci sono forti restrizioni contro più scritture simultanee nello stesso indirizzo di memoria. Un compilatore potrebbe avere difficoltà a coglierlo se avessimo a che fare con qualcosa di meno banale di f(i=-1, i=-1)
.
davidf fornisce anche un esempio di istruzioni interleaving molto simili a quelle degli harmic.
Sebbene ciascuno degli esempi di harmic, supercat e davidf sia in qualche modo inventato, presi insieme servono comunque a fornire una ragione tangibile per cui f(i=-1, i=-1)
dovrebbe essere un comportamento indefinito.
Ho accettato la risposta di harmic perché ha fatto il miglior lavoro per affrontare tutti i significati del perché, anche se la risposta di Paul Draper ha affrontato meglio la parte "per quale causa".
altre risposte
JohnB sottolinea che se consideriamo operatori di assegnazione sovraccaricati (anziché solo semplici scalari), allora potremmo anche avere problemi.
f(i-1, i = -1)
o qualcosa di simile.
std::nullptr_t
e le versioni qualificate per cv di questi tipi (3.9.3) sono chiamati collettivamente tipi scalari . "