Comportamento indefinito e punti di sequenza ricaricati


84

Considera questo argomento un sequel del seguente argomento:

Puntata precedente
Comportamento indefinito e punti di sequenza

Rivisitiamo questa espressione divertente e contorta (le frasi in corsivo sono prese dall'argomento sopra * smile *):

i += ++i;

Diciamo che questo richiama un comportamento indefinito. Presumo che quando si dice questo, si presume implicitamente che il tipo di isia uno dei tipi predefiniti.

E se il tipo di iè un tipo definito dall'utente? Diciamo che il suo tipo è Indexdefinito più avanti in questo post (vedi sotto). Invocerebbe ancora un comportamento indefinito?

Se sì, perché? Non è equivalente alla scrittura i.operator+=(i.operator++());o anche sintatticamente più semplice i.add(i.inc());? Oppure invocano anche un comportamento indefinito?

Se no, perché no? Dopo tutto, l'oggetto iviene modificato due volte tra punti di sequenza consecutivi. Ricorda la regola pratica: un'espressione può modificare il valore di un oggetto solo una volta tra "punti di sequenza" consecutivi . E se i += ++iè un'espressione, deve invocare un comportamento non definito. In tal caso, i suoi equivalenti i.operator+=(i.operator++());e i.add(i.inc());deve anche richiamare un comportamento non definito che sembra essere falso! (per quanto ho capito)

Oppure i += ++inon è un'espressione per cominciare? In caso affermativo, cos'è e qual è la definizione di espressione ?

Se è un'espressione, e allo stesso tempo, è il suo comportamento anche ben definito, allora implica che il numero di punti sequenza associati con un'espressione dipende in qualche modo il tipo di operandi coinvolte nell'espressione. Ho ragione (anche in parte)?


A proposito, che ne dici di questa espressione?

//Consider two cases:
//1. If a is an array of a built-in type
//2. If a is user-defined type which overloads the subscript operator!

a[++i] = i; //Taken from the previous topic. But here type of `i` is Index.

Devi considerare anche questo nella tua risposta (se conosci con certezza il suo comportamento). :-)


È

++++++i;

ben definito in C ++ 03? Dopotutto, questo è questo,

((i.operator++()).operator++()).operator++();

class Index
{
    int state;

    public:
        Index(int s) : state(s) {}
        Index& operator++()
        {
            state++;
            return *this;
        }
        Index& operator+=(const Index & index)
        {
            state+= index.state;
            return *this;
        }
        operator int()
        {
            return state;
        }
        Index & add(const Index & index)
        {
            state += index.state;
            return *this;
        }
        Index & inc()
        {
            state++;
            return *this;
        }
};

13
+1 grande domanda, che ha ispirato ottime risposte. Sento che dovrei dire che è ancora un codice orribile che dovrebbe essere modificato per essere più leggibile, ma probabilmente lo sai comunque :)
Philip Potter

4
@ Qual è la domanda: chi ha detto che è lo stesso? o chi ha detto che non è lo stesso? Non dipende da come li implementate? (Nota: presumo che il tipo di ssia un tipo definito dall'utente!)
Nawaz

5
Non vedo alcun oggetto scalare modificato due volte tra due punti di sequenza ...
Johannes Schaub - litb

3
@Johannes: allora si tratta di oggetti scalari . Che cos'è? Mi chiedo perché non ne ho mai sentito parlare prima. Forse perché i tutorial / C ++ - faq non lo menzionano o non lo enfatizzano? È diverso dagli oggetti di tipo incorporato ?
Nawaz

3
@Phillip: Ovviamente, non scriverò questo codice nella vita reale; infatti, nessun programmatore sano di mente lo scriverà. Queste domande sono solitamente concepite in modo da poter comprendere meglio l'intera faccenda del comportamento non definito e dei punti di sequenza! :-)
Nawaz

Risposte:


48

Sembra il codice

i.operator+=(i.operator ++());

Funziona perfettamente per quanto riguarda i punti di sequenza. La sezione 1.9.17 dello standard ISO C ++ dice questo sui punti di sequenza e sulla valutazione delle funzioni:

Quando si chiama una funzione (indipendentemente dal fatto che la funzione sia inline o meno), c'è un punto di sequenza 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. C'è anche un punto di sequenza dopo la copia di un valore restituito e prima dell'esecuzione di qualsiasi espressione al di fuori della funzione.

Ciò indicherebbe, ad esempio, che i.operator ++()il parametro as operator +=ha un punto di sequenza dopo la sua valutazione. In breve, poiché gli operatori sovraccaricati sono funzioni, si applicano le normali regole di sequenziamento.

Ottima domanda, comunque! Mi piace molto il modo in cui mi costringi a comprendere tutte le sfumature di una lingua che già credevo di conoscere (e pensavo che pensavo di conoscere). :-)



11

Come altri hanno già detto, il tuo i += ++iesempio funziona con il tipo definito dall'utente poiché stai chiamando funzioni e le funzioni comprendono punti di sequenza.

D'altra parte, a[++i] = inon è così fortunato supponendo che asia il tuo tipo di array di base, o anche uno definito dall'utente. Il problema che hai qui è che non sappiamo quale parte dell'espressione che contiene iviene valutata per prima. Potrebbe essere che ++ivenga valutato, passato a operator[](o alla versione grezza) per recuperare l'oggetto lì, e quindi il valore di iviene passato a quello (che è dopo che sono stato incrementato). D'altra parte, forse quest'ultimo lato viene valutato per primo, archiviato per un successivo assegnamento, quindi la ++iparte viene valutata.


quindi ... il risultato è quindi non specificato piuttosto che UB, poiché l'ordine di valutazione delle espressioni non è specificato?
Philip Potter

@Philip: unspecified significa che ci aspettiamo che il compilatore specifichi il comportamento, mentre undefined non pone tale obbligo. Penso che qui non sia definito, per lasciare ai compilatori più spazio per le ottimizzazioni.
Matthieu M.

@ Noah: ho anche postato una risposta. Per favore, dai un'occhiata e fammi sapere i tuoi pensieri. :-)
Nawaz

1
@Philip: il risultato è UB, a causa della regola in 5/4: "I requisiti di questo paragrafo devono essere soddisfatti per ogni ordinamento consentito delle sottoespressioni di un'espressione completa; altrimenti il ​​comportamento è indefinito.". Se tutti gli ordinamenti consentiti avessero punti di sequenza tra la modifica ++ie la lettura di isull'RHS dell'assegnazione, l'ordine non sarebbe specificato. Poiché uno degli ordinamenti consentiti fa queste due cose senza un punto di sequenza intermedio, il comportamento non è definito.
Steve Jessop

1
@Philip: Non si limita a definire un comportamento non specificato come comportamento indefinito. Anche in questo caso, se la gamma di comportamento non specificato include alcuni che non è definito, quindi il comportamento generale è indefinito. Se l'intervallo di comportamento non specificato è definito in tutte le possibilità, quindi comportamento generale è specificato. Ma hai ragione sul secondo punto, stavo pensando a un abuiltin e definito dall'utente i.
Steve Jessop

8

Penso che sia ben definito:

Dalla bozza di standard C ++ (n1905) §1.9 / 16:

"C'è anche un punto di sequenza dopo la copia di un valore restituito e prima dell'esecuzione di qualsiasi espressione al di fuori della funzione13). Diversi contesti in C ++ causano la valutazione di una chiamata di funzione, anche se nell'unità di traduzione non compare alcuna sintassi della chiamata di funzione corrispondente. [ Esempio : la valutazione di una nuova espressione richiama una o più funzioni di allocazione e di costruzione; vedere 5.3.4. Per un altro esempio, l'invocazione di una funzione di conversione (12.3.2) può verificarsi in contesti in cui non compare la sintassi della chiamata di funzione. - fine esempio ] I punti della sequenza all'entrata e all'uscita dalla funzione (come descritto sopra) sono caratteristiche delle chiamate di funzione come valutate, qualunque sia la sintassi dell'espressioneche chiama la funzione potrebbe essere. "

Nota la parte che ho messo in grassetto. Ciò significa che c'è effettivamente un punto di sequenza dopo la chiamata della funzione di incremento ( i.operator ++()) ma prima della chiamata di assegnazione composta ( i.operator+=).


6

Tutto a posto. Dopo aver esaminato le risposte precedenti, ho ripensato alla mia domanda, in particolare a questa parte a cui solo Noah ha tentato di rispondere ma non sono completamente convinto con lui.

a[++i] = i;

Caso 1:

If aè un array di tipo incorporato. Allora quello che ha detto Noè è corretto. Questo è,

a [++ i] = i non è così fortunato supponendo che a sia il tuo tipo di array di base, o anche uno definito dall'utente . Il problema che hai qui è che non sappiamo quale parte dell'espressione contenente i viene valutata per prima.

Quindi a[++i]=iinvoca un comportamento non definito o il risultato non è specificato. Qualunque cosa sia, non è ben definita!

PS: nella citazione sopra, strike-through è ovviamente mio.

Caso 2:

Se aè un oggetto di tipo definito dall'utente che esegue l'overload di operator[], allora di nuovo ci sono due casi.

  1. Se il tipo restituito della operator[]funzione sovraccaricata è di tipo incorporato, a[++i]=irichiama di nuovo un comportamento non definito o il risultato non è specificato.
  2. Ma se il tipo di ritorno di sovraccarico operator[]funzione è di tipo definito dall'utente, allora il comportamento di a[++i] = iben definito (per quanto ho capito), poiché in questo caso a[++i]=iè equivalente alla scrittura a.operator[](++i).operator=(i);che è lo stesso, a[++i].operator=(i);. Cioè, l'assegnazione operator=viene invocata sull'oggetto restituito di a[++i], che sembra essere molto ben definito, poiché al momento i a[++i]ritorni, ++isono già stati valutati, e quindi l' oggetto restituito chiama la operator=funzione passando il valore aggiornato di icome argomento. Notare che c'è un punto di sequenza tra queste due chiamate . E la sintassi garantisce che non vi sia concorrenza tra queste due chiamate eoperator[]sarebbe stato richiamato per primo e, consecutivamente, l'argomento ++ipassato al suo interno, sarebbe stato valutato per primo.

Pensa a questo come someInstance.Fun(++k).Gun(10).Sun(k).Tun();in cui ogni chiamata di funzione consecutiva restituisce un oggetto di un tipo definito dall'utente. Per me, questa situazione sembra più simile a questa:, eat(++k);drink(10);sleep(k)perché in entrambe le situazioni, esiste un punto di sequenza dopo ogni chiamata di funzione.

Per favore correggimi se sbaglio. :-)


1
@Nawaz k++e nonk sono separati da punti di sequenza. Entrambi possono essere valutati prima o vengono valutati. Il linguaggio richiede solo che venga valutato prima , non che gli argomenti di vengono valutati prima degli argomenti di. Sto in qualche modo spiegando di nuovo la stessa cosa senza essere in grado di fornire un riferimento, quindi non procediamo da qui. SunFunFunSunFunSun
Philip Potter

1
@Nawaz: perché non c'è nulla che definisca un punto di sequenza che li separa. Ci sono punti di sequenza prima e dopo l' Sunesecuzione, ma Funl'argomento di ++kpuò essere valutato prima o dopo. Ci sono punti di sequenza prima e dopo l' Funesecuzione, ma Sunl'argomento di kpuò essere valutato prima o dopo. Pertanto, un caso possibile è che sia ke ++ksiano valutati prima di uno Suno Funsiano valutati, quindi entrambi sono prima dei punti di sequenza di chiamata di funzione, e quindi non vi è alcun punto di sequenza che separa ke ++k.
Philip Potter

1
@Philip: ripeto: come è diversa questa situazione eat(i++);drink(10);sleep(i);? ... anche adesso si potrebbe dire i++può essere valutato prima o dopo?
Nawaz

1
@Nawaz: come posso essere più chiaro? Nell'esempio Fun / Sun, non vi è alcun punto di sequenza tra ke ++k. Nell'esempio bere / mangiare, non v'è come punto di sequenza tra ie i++.
Philip Potter

3
@Philip: non ha alcun senso. Tra Fun () e Sun () esiste un punto sequenza, ma tra i loro argomenti non esistono punti sequenza. È come dire, tra eat()ed sleep()esiste punto (i) sequenza (i), ma tra questi argomenti non neppure uno. Come possono gli argomenti di due chiamate di funzione separate da punti di sequenza, appartenere agli stessi punti di sequenza?
Nawaz
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.