Originariamente inviato da Who am I
Dice che in questo modo il compilatore non si deve preoccupare di controllare che un' area di memoria non sia stata cambiata da qualche puntatore.
Il motivo è esattamente questo. In generale, il fatto che il C consenta di usare in maniera libera e massiccia i puntatori complica il lavoro di ottimizzazione: infatti se io ho un codice di questo genere:
codice:
int funzione(int * ptr1, int * ptr2)
{
int a = 20;
*ptr2=15;
a+=*ptr1;
... tante altre operazioni con ptr1 e ptr2 ...
return a;
}
nel caso in cui ptr1 e ptr2 puntano ad elementi diversi il compilatore potrebbe semplicemente piazzare *ptr1 e *ptr2 in due registri diversi e ottimizzare così buona parte delle operazioni che seguono, copiando dai registri in memoria i valori "definitivi" di *ptr1 e *ptr2 giusto alla fine della funzione.
Il problema si ha quando ptr2 e ptr1 puntano alla stessa cosa (si ha cioè aliasing): in tal caso piazzare *ptr1 e *ptr2 in due registri e trattarli come due oggetti separati produce risultati sbagliati:
codice:
*ptr1=*ptr2=50;
*ptr1+=50;
*ptr2+=50;
se *ptr1 e *ptr2 puntano alla stessa roba il risultato finale sarà 150, ma se il compilatore assume che puntino a due oggetti distinti e fa un'ottimizzazione del tipo spiegato sopra si otterrà che la variabile a cui puntano sia ptr1 che ptr2 di fatto conterrà 100, risultato ovviamente sbagliato.
Per questo motivo di fatto prima che venga riletto *ptr1 (o *ptr2) anche l'altro deve essere scritto in memoria, per assicurarsi che il codice funzioni correttamente anche in caso di aliasing.
Questo ovviamente in certi casi dà delle penalizzazioni sensibili in termini di performance, dato che continuare a scrivere e leggere in memoria è molto più lento che usare semplicemente due registri - e tutto per coprire il caso (che potrebbe essere estremamente raro o addirittura non accadere mai) in cui ptr1 e ptr2 puntano alla stessa roba.
Per ovviare a questo "problema" si possono dare al compilatore delle garanzie su dove non ci può essere aliasing, in modo che possa ottimizzare al meglio:
- praticamente ogni compilatore ha una qualche genere di estensione del linguaggio per marcare un parametro come "senza aliasing" (garanzia che si può fornire ad esempio controllando da una funzione esterna che i due parametri siano diversi e poi richiamare la funzione vera e propria, gestendo separatamente il caso con aliasing);
- credo che buona parte degli optimizer attuali siano sensibili a check del tipo if(ptr1==ptr2) return; o roba del genere, per cui sanno che da lì in poi su ptr1 e ptr2 sicuramente non hanno aliasing;
- ma a monte di tutti questi dettagli implementativi, lo standard fornisce una garanzia fondamentale ai compilatori perché possano in generale semplificarsi la vita, ovvero la strict aliasing rule: se due puntatori sono di tipi diversi, allora non è consentito che puntino alla stessa roba (con un'eccezione per char * e unsigned char *, dato che sono spesso usati come buffer per storage/serializzazione/trasporto & co.) (in realtà le regole sono al solito più puntigliose e coprono diversi corner case, vedi [C++11, basic.lval, ¶10] per maggiori dettagli; nota che in C è leggermente diversa, si parla di "tipi compatibili");
Però ice che l' alternativa è usare le unioni, ma se ho un' unione come questa:
L'alternativa in teoria non è usare le unioni; su praticamente ogni compilatore questo uso delle union è consentito, ma per lo standard è undefined behavior, poiché è consentito solo leggere dall'ultimo membro in cui si è scritto (le union in effetti nascono per poter avere qualche genere di polimorfismo in C, non per "reinterpretare" tipi in altra maniera).
Sostanzialmente l'unico metodo consentito dallo standard per "reinterpretare" un tipo come un altro senza avere aliasing è passare per i char *, e quindi di fatto si passa per la memcpy:
codice:
#if sizeof(int)!=sizeof(float)
#error Panico e morte!
#endif
float f=0.5;
int i;
memcpy((char *)&i, (char *)&f, sizeof(i));
Per il tuo caso specifico di uso di un buffer di char come "backing storage" per un array di int, la questione dal punto di vista "legalese" è curiosamente pelosa, perché è vero che c'è un'eccezione alla strict aliasing rule per accedere tramite un char * ad altra roba, ma non dovrebbe valere l'opposto; per essere veramente in linea con le regole credo che piuttosto dovrebbe essere:
codice:
int ptr[N/4];
char * buffer= (char *)&ptr;
che dovrebbe dare sostanzialmente lo stesso effetto senza violare la strict aliasing rule.