Domanda

Prova il seguente codice:

#include <stdio.h>
#include <stdlib.h>
main()
{
    const char *yytext="0";
    const float f=(float)atof(yytext);
    size_t t = *((size_t*)&f);
    printf("t should be 0 but is %d\n", t);
}

Compilalo con:

gcc -O3 test.c

L'output BUONO dovrebbe essere:

"t should be 0 but is 0"

Ma con il mio gcc 4.1.3, ho:

"t should be 0 but is -1209357172"
È stato utile?

Soluzione

Utilizza il flag del compilatore -fno-strict-aliasing.

Con l'aliasing rigoroso abilitato, come per impostazione predefinita almeno per -O3, nella riga:

size_t t = *((size_t*)&f);

il compilatore presuppone che size_t* NON punti alla stessa area di memoria di float*.Per quanto ne so, questo è un comportamento conforme agli standard (l'aderenza alle rigide regole di aliasing nello standard ANSI inizia intorno a gcc-4, come ha sottolineato Thomas Kammeyer).

Se ricordo bene, puoi utilizzare un cast intermedio su char* per aggirare questo problema.(il compilatore presuppone che char* possa essere un alias di qualsiasi cosa)

In altre parole, prova questo (non posso testarlo personalmente in questo momento ma penso che funzionerà):

size_t t = *((size_t*)(char*)&f);

Altri suggerimenti

Nello standard C99, questo è coperto dalla seguente regola in 6.5-7:

Un oggetto deve avere il suo valore memorizzato accessibile solo da un'espressione di LValue che ha uno dei seguenti tipi: 73)

  • un tipo compatibile con il tipo effettivo dell'oggetto,

  • una versione qualificata di un tipo compatibile con il tipo effettivo dell'oggetto,

  • un tipo che è il tipo firmato o non firmato corrispondente al tipo effettivo dell'oggetto,

  • un tipo che è il tipo firmato o non firmato corrispondente a una versione qualificata del tipo efficace dell'oggetto,

  • un tipo aggregato o sindacale che include uno dei tipi sopra menzionati tra i suoi membri (inclusi, ricorsivamente, un membro di un sottoaggregato o un sindacato contenuto) o

  • un tipo di carattere.

L'ultimo elemento è il motivo per cui il casting first to a (char*) funziona.

Ciò non è più consentito secondo le regole C99 sull'aliasing dei puntatori.Puntatori di due tipi diversi non possono puntare alla stessa posizione in memoria.Le eccezioni a questa regola sono i puntatori void e char.

Quindi nel tuo codice in cui stai trasmettendo a un puntatore di size_t, il compilatore può scegliere di ignorarlo.Se vuoi ottenere il valore float come size_t, assegnalo e il float verrà lanciato (troncato e non arrotondato) come tale:

taglia_t taglia = (taglia_t)(f);// funziona

Questo viene comunemente segnalato come un bug, ma in realtà è una funzionalità che consente agli ottimizzatori di lavorare in modo più efficiente.

In gcc puoi disabilitarlo con un'opzione del compilatore.Credo che -fno_strict_aliasing.

È un cattivo codice C :-)

La parte problematica è che si accede a un oggetto di tipo float trasformandolo in un puntatore intero e dereferenziandolo.

Ciò infrange la regola dell'aliasing.Il compilatore è libero di presupporre che i puntatori a tipi diversi come float o int non si sovrappongano in memoria.Hai fatto esattamente questo.

Ciò che vede il compilatore è che calcoli qualcosa, lo memorizzi nel float f e non vi accedi mai più.Molto probabilmente il compilatore ha rimosso parte del codice e l'assegnazione non è mai avvenuta.

Il dereferenziamento tramite il puntatore size_t in questo caso restituirà della spazzatura non inizializzata dallo stack.

Puoi fare due cose per risolvere questo problema:

  1. usa un'unione con un float e un membro size_t ed esegui il casting tramite il gioco di parole.Non carino ma funziona.

  2. usa memcopy per copiare il contenuto di f nel tuo size_t.Il compilatore è abbastanza intelligente da rilevare e ottimizzare questo caso.

Perché dovresti pensare che t dovrebbe essere 0?

O, più precisamente, "Perché dovresti pensare che la rappresentazione binaria di uno zero in virgola mobile sia la stessa della rappresentazione binaria di uno zero intero?"

Questo è un codice C errato.Il tuo cast infrange le regole di aliasing C e l'ottimizzatore è libero di fare cose che infrangono questo codice.Probabilmente scoprirai che GCC ha programmato la lettura size_t prima della scrittura in virgola mobile (per nascondere la latenza della pipeline fp).

È possibile impostare l'opzione -fno-strict-aliasing oppure utilizzare un'unione o un reinterpret_cast per reinterpretare il valore in modo conforme agli standard.

A parte gli allineamenti del puntatore, ti aspetti che sizeof(size_t)==sizeof(float).Non penso che lo sia (su Linux a 64 bit size_t dovrebbe essere 64 bit ma float 32 bit), il che significa che il tuo codice leggerà qualcosa non inizializzato.

-O3 non è considerato "sano", -O2 è generalmente la soglia superiore tranne forse per alcune app multimediali.

Alcune app non possono nemmeno arrivare così lontano e muoiono se vai oltre -O1 .

Se hai un GCC abbastanza nuovo (qui sono alla 4.3), potrebbe supportare questo comando

  gcc -c -Q -O3 --help=optimizers > /tmp/O3-opts

Se stai attento, probabilmente sarai in grado di esaminare l'elenco e trovare la singola ottimizzazione che stai abilitando e che causa questo bug.

Da man gcc :

  The output is sensitive to the effects of previous command line options, so for example it is possible to find out which
       optimizations are enabled at -O2 by using:

               -O2 --help=optimizers

       Alternatively you can discover which binary optimizations are enabled by -O3 by using:

               gcc -c -Q -O3 --help=optimizers > /tmp/O3-opts
               gcc -c -Q -O2 --help=optimizers > /tmp/O2-opts
               diff /tmp/O2-opts /tmp/O3-opts | grep enabled

Ho testato il tuo codice con:"i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc.costruire 5465)"

e non c'era nessun problema.Produzione:

t should be 0 but is 0

Quindi non c'è un bug nel tuo codice.Ciò non significa che sia un buon codice.Ma aggiungerei il returnType della funzione principale e il "return 0;" Alla fine della funzione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top