web-dev-qa-db-fra.com

"Le déréférencement d'un pointeur avec punition de type enfreindra les règles strictes d'alias"

J'utilise un code où je jette une énumération * en int *. Quelque chose comme ça:

enum foo { ... }
...
foo foobar;
int *pi = reinterpret_cast<int*>(&foobar);

Lors de la compilation du code (g ++ 4.1.2), j'obtiens le message d'avertissement suivant:

dereferencing type-punned pointer will break strict-aliasing rules

J'ai googlé ce message et constaté qu'il ne se produit que lorsque l'optimisation stricte de l'aliasing est activée. J'ai les questions suivantes:

  • Si je laisse le code avec cet avertissement, cela générera-t-il un code potentiellement incorrect?
  • Existe-t-il un moyen de contourner ce problème?
  • S'il n'y en a pas, est-il possible de désactiver l'alias strict depuis l'intérieur du fichier source (car je ne veux pas le désactiver pour tous les fichiers source et je ne veux pas créer une règle Makefile distincte pour ce fichier source )?

Et oui, j'ai vraiment besoin de ce type d'alias.

53
petersohn

En ordre:

  • Oui. GCC supposera que les pointeurs ne peuvent pas être alias. Par exemple, si vous attribuez par l'un puis lisez de l'autre, GCC peut, comme une optimisation, réorganiser la lecture et l'écriture - j'ai vu cela se produire dans le code de production, et ce n'est pas agréable à déboguer.

  • Nombreuses. Vous pouvez utiliser une union pour représenter la mémoire dont vous avez besoin pour réinterpréter. Vous pouvez utiliser un reinterpret_cast. Vous pouvez transtyper via char * Au point où vous réinterprétez la mémoire - char * Sont définis comme pouvant alias n'importe quoi. Vous pouvez utiliser un type qui a __attribute__((__may_alias__)). Vous pouvez désactiver les hypothèses d'alias globalement en utilisant -fno-strict-aliasing.

  • __attribute__((__may_alias__)) sur les types utilisés est probablement la méthode la plus proche de désactiver l'hypothèse pour une section particulière de code.

Pour votre exemple particulier, notez que la taille d'une énumération est mal définie; GCC utilise généralement la plus petite taille d'entier qui peut être utilisée pour le représenter, donc réinterpréter un pointeur vers une énumération comme un entier peut vous laisser des octets de données non initialisés dans l'entier résultant. Ne fais pas ça. Pourquoi ne pas simplement transtyper en un type entier suffisamment grand?

57
moonshadow

Mais pourquoi fais-tu ça? Il se cassera si sizeof (foo)! = Sizeof (int). Ce n'est pas parce qu'une énumération est comme un entier qu'elle est stockée comme telle.

Alors oui, cela pourrait générer un code "potentiellement" incorrect.

11
CashCow

Vous pouvez utiliser le code suivant pour caster vos données:

template<typename T, typename F>
struct alias_cast_t
{
    union
    {
        F raw;
        T data;
    };
};

template<typename T, typename F>
T alias_cast(F raw_data)
{
    alias_cast_t<T, F> ac;
    ac.raw = raw_data;
    return ac.data;
}

Exemple d'utilisation:

unsigned int data = alias_cast<unsigned int>(raw_ptr);
11
Markus Lenger

Avez-vous étudié cette réponse ?

La règle d'aliasing stricte rend cette configuration illégale, deux types indépendants ne peuvent pas pointer vers la même mémoire. Seul char * a ce privilège . Malheureusement, vous pouvez toujours coder de cette manière, peut-être obtenir des avertissements, mais compiler correctement.

5
icecrime

L'alias strict est une option du compilateur, vous devez donc le désactiver à partir du makefile.

Et oui, il peut générer du code incorrect. Le compilateur supposera effectivement que foobar et pi ne sont pas liés ensemble, et supposera que *pi ne changera pas si foobar a changé.

Comme déjà mentionné, utilisez static_cast à la place (et pas de pointeurs).

2
Let_Me_Be