gcc, strict-aliasing e casting attraverso un’unione

Hai qualche storia dell’orrore da raccontare? Il manuale GCC ha recentemente aggiunto un avvertimento riguardante -fitture-aliasing e il cast di un puntatore tramite un’unione:

[…] Prendendo l’indirizzo, lanciare il puntatore risultante e dereferenziare il risultato ha un comportamento indefinito [enfasi aggiunta], anche se il cast usa un tipo di unione, ad esempio:

union a_union { int i; double d; }; int f() { double d = 3.0; return ((union a_union *)&d)->i; } 

Qualcuno ha un esempio per illustrare questo comportamento indefinito?

Nota che questa domanda non riguarda ciò che lo standard C99 dice, o non dice. Riguarda il funzionamento effettivo di gcc e di altri compilatori esistenti, oggi.

Sto solo supponendo, ma un potenziale problema potrebbe risiedere nell’impostazione di d a 3.0. Poiché d è una variabile temporanea che non viene mai letta direttamente e che non viene mai letta tramite un puntatore “abbastanza compatibile”, il compilatore potrebbe non occuparsi di impostarlo. E quindi f () restituirà alcuni rifiuti dalla pila.

    Il mio tentativo semplice, ingenuo, fallisce. Per esempio:

     #include  union a_union { int i; double d; }; int f1(void) { union a_union t; td = 3333333.0; return ti; // gcc manual: 'type-punning is allowed, provided...' (C90 6.3.2.3) } int f2(void) { double d = 3333333.0; return ((union a_union *)&d)->i; // gcc manual: 'undefined behavior' } int main(void) { printf("%d\n", f1()); printf("%d\n", f2()); return 0; } 

    funziona bene, dando su CYGWIN:

     -2147483648 -2147483648 

    Osservando l’assemblatore, vediamo che gcc ottimizza completamente t : f1() memorizza semplicemente la risposta pre-calcasting:

     movl $-2147483648, %eax 

    mentre f2() spinge 3333333.0 nello stack a virgola mobile e quindi estrae il valore restituito:

     flds LC0 # LC0: 1246458708 (= 3333333.0) (--> 80 bits) fstpl -8(%ebp) # save in d (64 bits) movl -8(%ebp), %eax # return value (32 bits) 

    E le funzioni sono anche in linea (che sembra essere la causa di alcuni sottili bug aliasing rigidi), ma non è rilevante qui. (E questo assemblatore non è rilevante, ma aggiunge dettagli di conferma).

    Si noti inoltre che prendere gli indirizzi è ovviamente sbagliato (o giusto , se si sta tentando di illustrare un comportamento non definito). Ad esempio, proprio come sappiamo questo è sbagliato:

     extern void foo(int *, double *); union a_union t; td = 3.0; foo(&t.i, &t.d); // undefined behavior 

    allo stesso modo sappiamo che questo è sbagliato:

     extern void foo(int *, double *); double d = 3.0; foo(&((union a_union *)&d)->i, &d); // undefined behavior 

    Per una discussione generale su questo, vedi ad esempio:

    http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1422.pdf
    http://gcc.gnu.org/ml/gcc/2010-01/msg00013.html
    http://davmac.wordpress.com/2010/02/26/c99-revisited/
    http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html
    (= cerca la pagina su Google quindi visualizza la pagina in cache)

    Qual è la regola rigorosa di aliasing?
    Regole di aliasing rigoroso C99 in C ++ (GCC)

    Nel primo collegamento, bozza di verbale di una riunione ISO sette mesi fa, un partecipante osserva nella sezione 4.16:

    C’è qualcuno che pensa che le regole siano abbastanza chiare? Nessuno è veramente in grado di interpretarli.

    Altre note: il mio test è stato con gcc 4.3.4, con -O2; le opzioni -O2 e -O3 implicano -al contrario-aliasing. L’esempio dal Manuale GCC assume sizeof (double) > = sizeof (int); non importa se sono ineguali.

    Inoltre, come notato da Mike Acton nel link cellperformace, -Wstrict-aliasing=2 , ma non =3 , produce un warning: dereferencing type-punned pointer might break strict-aliasing rules per l’esempio qui.