45 votes

Le pointeur de déréférencement typé va enfreindre les règles de crénelage strict

J'ai utilisé le code suivant pour lire des données à partir de fichiers dans le cadre d'un programme plus vaste.

double data_read(FILE *stream,int code) {
        char data[8];
        switch(code) {
        case 0x08:
            return (unsigned char)fgetc(stream);
        case 0x09:
            return (signed char)fgetc(stream);
        case 0x0b:
            data[1] = fgetc(stream);
            data[0] = fgetc(stream);
            return *(short*)data;
        case 0x0c:
            for(int i=3;i>=0;i--)
                data[i] = fgetc(stream);
            return *(int*)data;
        case 0x0d:
            for(int i=3;i>=0;i--)
                data[i] = fgetc(stream);
            return *(float*)data;
        case 0x0e:
            for(int i=7;i>=0;i--)
                data[i] = fgetc(stream);
            return *(double*)data;
        }
        die("data read failed");
        return 1;
    }

Maintenant, je suis obligé d'utiliser -O2 et je reçois la suite gcc avertissement: warning: dereferencing type-punned pointer will break strict-aliasing rules

Googleing j'ai trouvé deux orthogonaux réponses:

vs

En fin de compte je ne veux pas ignorer les avertissements. Que recommanderiez-vous?

[mise à jour] j'ai remplacé le jouet exemple avec la fonction réelle.

39voto

Lasse Reinhold Points 171

Le problème se produit car vous avez accès à un char-tableau par le biais d'un double*:

char data[8];
...
return *(double*)data;

Mais gcc suppose que votre programme ne sera jamais accéder à des variables mais des pointeurs de type différent. Cette hypothèse est appelée strict-aliasing et permet au compilateur de faire quelques optimisations:

Si le compilateur sait que votre *(double*) peut en aucun cas se chevaucher avec data[], il est permis à toutes sortes de choses comme la réorganisation de votre code:

return *(double*)data;
for(int i=7;i>=0;i--)
    data[i] = fgetc(stream);

La boucle est probablement optimisé loin et vous vous retrouvez avec juste:

return *(double*)data;

Ce qui laisse à vos données[] non initialisée. Dans ce cas particulier, le compilateur pourrait être en mesure de voir que votre pointeurs se chevauchent, mais si vous aviez déclaré char* data, il peut donner de bugs.

Mais, au strict-aliasing règle dit qu'un char* et void* peut pointer n'importe quel type. De sorte que vous pouvez réécrire en:

double data;
...
*(((char*)&data) + i) = fgetc(stream);
...
return data;

Stricte aliasing avertissements sont vraiment important de comprendre ou à résoudre. Ils provoquent les sortes d'insectes qui sont impossibles à reproduire à la maison car ils se produisent uniquement sur un particulier de compilateur sur un système d'exploitation sur une machine en particulier et sur une pleine lune et une fois par année, etc.

26voto

Martin B Points 14919

Il ressemble un peu comme si vous voulez vraiment utiliser fread:

int data;
fread(&data, sizeof(data), 1, stream);

Cela dit, si vous ne voulez pas suivre la voie de la lecture de caractères, puis à les réinterpréter comme un int, le plus sûr moyen de le faire est d'utiliser un syndicat:

union
{
    char theChars[4];
    int theInt;
} myunion;

for(int i=0; i<4; i++)
    myunion.theChars[i] = fgetc(stream);
return myunion.theInt;

Je ne suis pas sûr pourquoi, la longueur de l' data dans votre code d'origine est 3. Je suppose que vous vouliez 4 octets; au moins, je ne sais pas du tout les systèmes où un int est de 3 octets.

Il est à noter que votre code et le mien sont fortement non-portable.

Edit: Si vous voulez lire les ints de différentes longueurs à partir d'un fichier, de façon portable, essayez quelque chose comme ceci:

unsigned result=0;
for(int i=0; i<4; i++)
    result = (result << 8) | fgetc(stream);

(Remarque: Dans un programme réel, vous voulez également que pour tester la valeur de retour de fgetc() contre EOF).

Il lit un 4 octets non signés à partir du fichier au format little-endian, indépendamment de ce que le boutisme de la système est. Il devrait fonctionner sur n'importe quel système où un unsigned est d'au moins 4 octets.

Si vous voulez être endian-neutre, ne pas utiliser de pointeurs ou des syndicats; utiliser les bit-quarts de la place.

7voto

anon Points 61

L'utilisation d'un syndicat n'est pas la bonne chose à faire ici. La lecture d'un membre non écrit de l'union n'est pas définie. En d'autres termes, le compilateur est libre d'effectuer des optimisations qui vont casser votre code (par exemple, l'optimisation de l'écriture).

2voto

Jens Gustedt Points 40410

Fondamentalement, vous pouvez lire du ccg message comme gars que vous cherchez les ennuis, ne pas dire que je ne t'ai pas prévenu ya.

Casting trois octets tableau de caractères à un int est l'une des pires choses que j'ai vu, jamais. Normalement, votre int a au moins 4 octets. Donc, pour la quatrième (et peut-être plus si int est plus large), vous obtenez des données aléatoires. Et puis vous jeter tout cela à une double.

Il suffit de ne rien de tout cela. Le problème d'aliasing que gcc met en garde sur l'est innocent par rapport à ce que vous faites.

-4voto

Apparemment, la norme permet l'utilisation de sizeof(char*) doit être différente de sizeof(int*) afin de gcc se plaint quand vous essayez directement exprimés. void* est un peu spécial que tout peut être converti en arrière et de void*. Dans la pratique, je ne sais pas beaucoup de l'architecture/compilateur où un pointeur n'est pas toujours le même pour tous les types, mais gcc est en droit d'émettre un avertissement, même si c'est embêtant.

Je pense que la manière la plus sûre serait

int i, *p = &i;
char *q = (char*)&p[0];

ou

char *q = (char*)(void*)p;

Vous pouvez également essayer cela et voir ce que vous obtenez:

char *q = reinterpret_cast<char*>(p);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X