2016-03-15 8 views
6

Sulla base di questo ottimo post sul blog, The Strict Aliasing Situation is Pretty Bad, ho messo il pezzo di codice online per te di provarlo:C dà output diverso in base al livello di ottimizzazione (nuovo esempio)

http://cpp.sh/9kht (uscita cambia tra - O0 e -O2)

#include <stdio.h> 

long foo(int *x, long *y) { 
    *x = 0; 
    *y = 1; 
    return *x; 
} 

int main(void) { 
    long l; 
    printf("%ld\n", foo((int *)&l, &l)); 
} 
  • c'è una sorta di comportamento indefinito qui?

  • Cosa succede internamente quando scegliamo il livello -O2?

+1

Questo è in realtà un incredibile esempio di violazione della regola di aliasing di tipo. Lo farò il mio duplicato canonico. – SergeyA

risposta

12
  1. Sì, questo programma è indefinito comportamento, a causa delle tipo a base di aliasing regole, che possono essere riassunti come "non si può accedere a una posizione di memoria dichiarata con il tipo A tramite un puntatore di tipo B, eccetto quando B è un puntatore a un tipo di carattere (ad es. unsigned char *). " Questa è un'approssimazione, ma è abbastanza vicina per la maggior parte degli scopi. Si noti che quando A è un puntatore a un tipo di carattere, B potrebbe non essere qualcos'altro-sì, questo significa che l'idioma comune di accedere a un buffer di byte "quattro alla volta" tramite un uint32_t* è un comportamento non definito (il blog anche il post tocca questo).

  2. Il compilatore presuppone, durante la compilazione di foo, che x e non puntino allo stesso oggetto. Da ciò deduce che la scrittura tramite *y non può modificare il valore di *x e può solo restituire il valore noto di *x, 0, senza rileggerlo dalla memoria. Lo fa solo quando l'ottimizzazione è attiva perché tenere traccia di ciò che ogni puntatore può e non può puntare è costoso (quindi la compilazione è più lenta).

    Si noti che questo è un "demoni volare fuori del vostro naso" situazione: il compilatore ha il diritto di rendere il codice generato per foo partenza con

    cmp rx, ry 
    beq __crash_the_program 
    ... 
    

    (e uno strumento come UBSan potrebbe fare proprio questo)

+0

Ottima risposta @ zwol, grazie. Potrebbe desiderare di espandere un po 'di più le regole di aliasing basate sul tipo? (come dove sono nella specifica, altri esempi, ecc.) – Dave5545

+2

In realtà, 'char',' signed char' e 'unsigned char' sono * tre * diversi tipi ai quali è consentito accedere a tipi arbitrari. – EOF

+0

@ Dave5545 Lo farò quando sono sul computer su cui è presente la mia copia di C99. – zwol

1

Detto in altri termini, il codice (int *)&l dice trattare il puntatore come un puntatore a un int. Non converte nulla. Quindi, il (int *) dice al compilatore di permetterti di passare un * lungo a una funzione che si aspetta un * int. Stai mentendo ad esso. All'interno, foo si aspetta che x sia un puntatore a un int, ma non lo è. Il layout di memoria non è quello che dovrebbe essere. Come vedi, i risultati sono imprevedibili.

In un'altra nota, non utilizzerei mai l (ell) come nome di variabile. È troppo facilmente confuso con 1 (uno). Ad esempio, che cos'è questo?

int x = l; 
Problemi correlati