Domanda Bug GCC? Metodi concatenati, punto di sequenza spezzato


Ho eseguito il debug di un programma per un po 'di tempo e alla fine ho scoperto che l'errore era dovuto a un riferimento che non veniva aggiornato come pensavo sarebbe stato.

Ecco un esempio che mostra il problema che ho riscontrato:

#include <iostream>
using namespace std;

struct Test {
    Test& set(int& i){ i = 10; return *this; }
    Test& print(const int& i){ cout << i << endl; return *this; }
};

int main(void){
    int i = 0;
    Test t;

    t.set(i).print(i + 5);

    return 0;
}

Mi aspettavo che il metodo print () qui avrebbe prodotto 15, ma invece emette 5.

EDIT: 10 giorni dopo mi sono appena reso conto che con clang esce 15! È un bug in GCC?


15
2018-02-06 18:37


origine


risposte:


Lasciatemi provare a interpretare lo standard C ++ 11 su questo. Nel §1.9 / 15 dice:

Tranne dove indicato, le valutazioni degli operandi dei singoli operatori e delle sottoespressioni delle singole espressioni sono state eliminate. [...] Se un effetto collaterale su un oggetto scalare è ingiustificato rispetto a un altro effetto collaterale sullo stesso oggetto scalare o un calcolo del valore che utilizza il valore dello stesso oggetto scalare, il comportamento non è definito.

Certamente int è un tipo scalare e t.set(i).print(i + 5); contiene un effetto collaterale su i in set() e il calcolo del valore i + 5, quindi se non si fa notare diversamente, il comportamento è davvero indefinito. Leggendo §5.2.5 ("Accesso membri della classe"), non sono riuscito a trovare alcuna nota sulle sequenze riguardanti il . operatore. [Ma vedi modifica qui sotto!]

Si noti, tuttavia, che è ovviamente garantito set() è eseguito prima print() perché quest'ultimo riceve il valore di ritorno del primo come un (implicito this) discussione. Il colpevole qui è che il calcolo del valore per printLe argomentazioni sono non in sequenza sequenzialmente indeterminato rispetto alla chiamata di set.

EDIT: Dopo aver letto la risposta nel tuo commento (@ Xeno), rileggo il paragrafo nello standard, e in effetti dice più tardi:

Ogni valutazione nella funzione chiamante (incluse altre chiamate di funzioni) che non sia altrimenti specificatamente sequenziata prima o dopo l'esecuzione del corpo della funzione chiamata è sequenzialmente indeterminata rispetto all'esecuzione della funzione chiamata.

Perché sequenziale indeterminato non è non in sequenza ("l'esecuzione di valutazioni non eseguite può sovrapporsi", §1.9 / 13), questo non è in effetti un comportamento indefinito, ma "solo" un comportamento non specificato, il che significa che sia 15 che 5 sono risultati corretti.

Cosi quando < significa "sequenziato prima" e ~ significa "sequenzialmente indeterminato", abbiamo:

(value computations for print()'s arguments ~ execution of set()) < execution of print()


10
2018-02-16 13:09



Non vi è alcuna garanzia in C ++ sull'ordine in cui vengono valutati gli argomenti delle funzioni in una singola espressione, nemmeno quando queste funzioni sono chiamate di metodo concatenate. Stai invocando un comportamento indefinito qui ed è quello che ottieni.

Il . operatore fa implica il sequenziamento, ma solo nella misura in cui l'espressione prima del. deve essere valutato completamente prima di accedere al membro. Ciò non significa che le valutazioni delle sottoespressioni siano sospese fino a quel momento.

Inoltre, non passare ints di const int&, non c'è modo in cui questo potrebbe essere più veloce di passare un int direttamente (a meno che per qualche strana ragione intnon si adatta a una parola del processore e il riferimento lo fa).


2
2018-02-16 11:38



[troppo lungo per un commento:]

Se aggiungendo

Test& add(int& i, const int toadd)
{
  i += toadd;
  return *this;
}

Questa chiamata

t.set(i).add(i, 5).print(i);

ritorna

15

Da ciò concludo che il colpevole è il i + 5 come parametro da stampare.


0
2018-02-16 13:17