Question

Testez le code suivant:

#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);
}

Compilez-le avec:

gcc -O3 test.c

Le bon résultat devrait être:

"t should be 0 but is 0"

Mais avec mon gcc 4.1.3, j'ai:

"t should be 0 but is -1209357172"
Était-ce utile?

La solution

Utilisez l'indicateur de compilation -fno-strict-aliasing.

Avec l'aliasing strict activé, comme par défaut pour au moins -O3, dans la ligne:

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

le compilateur suppose que size_t * ne pointe PAS sur la même zone mémoire que le float *. Pour autant que je sache, il s’agit d’un comportement conforme aux normes (le respect des règles strictes en matière de repliement des normes ANSI commence autour de gcc-4, comme l’a souligné Thomas Kammeyer).

Si je me souviens bien, vous pouvez utiliser une conversion intermédiaire pour char * afin de résoudre ce problème. (le compilateur suppose que char * peut alias n'importe quoi)

En d'autres termes, essayez ceci (je ne peux pas le tester moi-même pour l'instant, mais je pense que cela fonctionnera):

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

Autres conseils

Dans la norme C99, cela est couvert par la règle suivante dans 6.5-7:

  

La valeur stockée d'un objet ne doit être accessible que par une expression lvalue possédant l'une des valeurs suivantes:   les types suivants: 73)

     
      
  • un type compatible avec le type effectif de l'objet,

  •   
  • une version qualifiée d'un type compatible avec le type effectif de l'objet,

  •   
  • un type qui est le type signé ou non signé correspondant au type effectif du   objet,

  •   
  • un type qui est le type signé ou non signé correspondant à une version qualifiée du   type effectif de l'objet,

  •   
  • un type d'agrégat ou d'union qui inclut l'un des types susmentionnés parmi ses   membres (y compris, de manière récursive, un membre d'une union restreinte ou confinée), ou

  •   
  • un type de caractère.

  •   

Le dernier élément explique pourquoi la conversion en premier sur un (caractère *) fonctionne.

Cela n’est plus autorisé selon les règles C99 sur les alias de pointeur. Les pointeurs de deux types différents ne peuvent pas pointer vers le même emplacement en mémoire. Les exceptions à cette règle sont null et les pointeurs sur caractères.

Le compilateur peut donc choisir de l'ignorer dans le code dans lequel vous lancez un pointeur de size_t. Si vous voulez obtenir la valeur float sous la forme size_t, assignez-la simplement et le float sera lancé (tronqué et non arrondi) comme tel:

size_t size = (size_t) (f); // ça marche

C’est communément signalé comme un bogue, mais c’est en fait une fonctionnalité qui permet aux optimiseurs de fonctionner plus efficacement.

Dans gcc, vous pouvez le désactiver avec un commutateur de compilateur. Je crois que -fno_strict_aliasing.

Le code C est mauvais: -)

La partie problématique est que vous accédez à un objet de type float en le convertissant en un pointeur d’entier et en le déréférencant.

Ceci enfreint la règle de crénelage. Le compilateur est libre de supposer que les pointeurs sur différents types tels que float ou int ne se chevauchent pas en mémoire. Vous avez fait exactement cela.

Ce que voit le compilateur, c'est que vous calculez quelque chose, le stockez dans le flottant f et n'y accédez plus jamais. Il est fort probable que le compilateur ait supprimé une partie du code et que l’affectation n’ait jamais eu lieu.

Le déréférencement via votre pointeur size_t renverra dans ce cas des déchets non initialisés de la pile.

Vous pouvez faire deux choses pour contourner ce problème:

  1. utilise une union avec un float et un membre size_t et effectue le casting via le type punning. Pas sympa mais ça marche.

  2. utilisez memcopy pour copier le contenu de f dans votre size_t. Le compilateur est suffisamment intelligent pour détecter et optimiser ce cas.

Pourquoi penseriez-vous que t devrait être 0?

Ou, plus précisément, "Pourquoi pensez-vous que la représentation binaire d'un zéro en virgule flottante serait identique à la représentation binaire d'un zéro entier?"

Ceci est un mauvais code C. Votre distribution casse les règles de crénelage C et l'optimiseur est libre de faire des choses qui cassent ce code. Vous constaterez probablement que GCC a programmé la taille_t lue avant l'écriture à virgule flottante (pour masquer la latence du pipeline fp).

Vous pouvez définir le commutateur -fno-strict-aliasing, ou utiliser une union ou un reinterpret_cast pour réinterpréter la valeur conformément aux normes.

Mis à part les alignements de pointeurs, vous vous attendez à ce que sizeof (size_t) == sizeof (float). Je ne pense pas que ce soit le cas (sur Linux 64 bits, size_t devrait être 64 bits, mais 32 bits libres), ce qui signifie que votre code lira quelque chose de non initialisé.

-O3 n'est pas considéré comme "sain", -O2 est généralement le seuil supérieur, sauf peut-être pour certaines applications multimédia.

Certaines applications ne peuvent même pas aller aussi loin et meurent si vous dépassez -O1.

Si vous avez assez de nouveau GCC (je suis en 4.3 ici), il peut supporter cette commande

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

Si vous faites attention, vous pourrez éventuellement parcourir cette liste et trouver l’optimisation singulière donnée que vous activez et qui provoque ce bogue.

À partir de 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

J'ai testé votre code avec: "i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5465)"

et il n'y avait pas de problème. Sortie:

t should be 0 but is 0

Donc, il n'y a pas de bogue dans votre code. Cela ne signifie pas que c'est un bon code. Mais je voudrais ajouter le type de retour de la fonction principale et le "return 0;" à la fin de la fonction.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top