2013-04-04 12 views
9

Questa è l'implementazione radice quadrata veloce inversa da Quake III Arena:Il cast del puntatore interrompe la regola di aliasing rigorosa?

float Q_rsqrt(float number) 
{ 
     long i; 
     float x2, y; 
     const float threehalfs = 1.5F; 

     x2 = number * 0.5F; 
     y = number; 
     i = * (long *) &y;      // evil floating point bit level hacking 
     i = 0x5f3759df - (i >> 1);    // what? 
     y = * (float *) &i; 
     y = y * (threehalfs - (x2 * y * y)); // 1st iteration 
//  y = y * (threehalfs - (x2 * y * y)); // 2nd iteration, this can be removed 

     return y; 
} 

ho notato che long inti assume il valore Dereferenced all'indirizzo (cast a un long *) di galleggiantey. Il codice esegue quindi le operazioni su i prima di memorizzare il valore di riferimento sull'indirizzo (assegnato a float *) di i in .

Questo interromperà la rigida regola di aliasing dal i non è dello stesso tipo di ?

Penso che forse non lo è poiché il valore è dereferenziato e copiato; quindi le operazioni vengono eseguite su una copia anziché sull'originale.

+0

Penso che certianamente lo faccia, almeno finchè 'sizeof (long)! = Sizeof (float)'. –

+1

'sizeof' non ha nulla a che fare con la violazione di aliasing. –

+0

Il codice come scritto assume 'long' è un valore a 32 bit, proprio come un' float'. Il cast del puntatore sembra insidioso, anche se non sono sicuro che sia vietato dal momento che il compilatore può "vedere" cosa sta succedendo [a differenza di uno scenario in cui un puntatore viene memorizzato da qualche parte e poi usato in un modo sorprendente]. Penso che i problemi di aliasing possano essere risolti tramite i cast intervenienti a '(unsigned char *)', senza generazione di codice nei casi che avrebbero funzionato comunque. La mia stessa inclinazione potrebbe essere quella di usare un 'unione' invece. Ancora non è davvero portatile, ma i compilatori dovrebbero aspettarsi che i sindacati siano pseudonimi. – supercat

risposta

6

Sì, questo codice è gravemente danneggiato e richiama un comportamento non definito. In particolare, si noti queste due linee:

y = number; 
    i = * (long *) &y;      // evil floating point bit level hacking 

Poiché l'oggetto *(long *)&y è digitare long, il compilatore è libera di assumere non può alias un oggetto di tipo float; quindi, il compilatore potrebbe riordinare queste due operazioni l'una rispetto all'altra.

Per risolvere il problema, è necessario utilizzare un raccordo.

1

i = * (long *) &y;

Questo rompe aliasing regole e invoca pertanto un comportamento indefinito.

Si accede all'oggetto con un tipo diverso da float o una variante firmata/non firmata di char.

y = * (float *) &i;

Questa dichiarazione di cui sopra le regole anche le pause aliasing.

4

Sì, interrompe le regole di aliasing.

Nella moderna C, è possibile modificare i = * (long *) &y; a:

i = (union { float f; long l; }) {y} .l; 

e y = * (float *) &i; a:

y = (union { long l; float f; }) {i} .f; 

Purché si disponga di garanzie che, nell'attuazione C in uso, long e float hanno adeguato dimensioni e rappresentazioni, quindi il comportamento è definito dallo standard C: i byte dell'oggetto di un tipo verranno reinterpretati come l'altro tipo.

+1

A proposito, 'long' è sicuramente il tipo sbagliato da usare qui. Dovrebbe essere 'uint32_t', o se si desidera assumere modelli ILP32/LP64,' int' o 'unsigned' sarebbero sufficienti. 'long' è definitivamente rotto su qualsiasi obiettivo a 64 bit del mondo reale ad eccezione di Windows. –

+0

@R .: 'long' essendo 64 bit viola il vincolo" si garantisce che, nell'implementazione C utilizzata, 'long' e' float' hanno dimensioni e rappresentazioni appropriate ". –

+0

Sono d'accordo. Stavo commentando la cattiveria del codice originale citato, non la tua risposta. Scusa se non era chiaro. –

3

Sì, interrompe le regole di aliasing.

La correzione più pulita per le cose come i = * (long *) &y; sarebbe questo:

memcpy(&i, &y, sizeof(i)); // assuming sizeof(i) == sizeof(y) 

Evita problemi con l'allineamento e aliasing. E con l'ottimizzazione abilitata, la chiamata a memcpy() dovrebbe essere normalmente sostituita con poche istruzioni.

Proprio come qualsiasi altro metodo suggerito, questo approccio non risolve alcun problema relativo alle rappresentazioni di trap. Sulla maggior parte delle piattaforme, tuttavia, non ci sono rappresentazioni di trap nei numeri interi e se si conosce il formato in virgola mobile è possibile evitare rappresentazioni di trap di formato in virgola mobile, se ce ne sono.

+0

Il problema con questo è che Q_rsqrt dovrebbe essere una rapida radice quadrata reciproca, quindi una chiamata a memcpy non è desiderabile. – RunHolt

+0

@RunHolt I compilatori di oggi non hanno problemi ad inlining di cose come 'memcpy()'. Ma l'ho già menzionato nella risposta. –

+0

@AlexeyFrunze: Anche se i compilatori possono ottimizzare 'memcpy' in un'istruzione di caricamento e memorizzazione, continuo a pensare che ci sia qualcosa di arretrato nell'idea che il codice che vuole fare una semplice operazione debba, per evitare che la sua funzionalità venga distrutta dall'ottimizzatore, richiedere un'operazione più complicata (e più difficile da leggere nel codice sorgente) e sperare che l'ottimizzatore lo trasformi in un codice che è buono come quello che un compilatore non ottimizzante avrebbe prodotto. – supercat

Problemi correlati