785 votes

Quelle est la règle stricte d'aliasing?

Quand on pose une question sur le comportement non défini commun en C , les âmes sont plus éclairées que je ne l'ai fait à propos de la règle de l'aliasing strict.
De quoi parlent-ils?

553voto

Doug T. Points 33360

Une situation typique que vous rencontrez stricte problèmes d'aliasing, c'est quand superposition d'un struct (comme un périphérique de réseau/msg) sur un tampon de la taille de mot de votre système (comme un pointeur vers uint32_ts ou uint16_ts). Lorsque vous superposez une structure sur un tel tampon, ou un tampon sur une telle structure au travers de l'aiguille de la coulée, vous pouvez facilement violer stricte aliasing règles.

Donc dans ce genre de configuration, si je veux envoyer un message à quelque chose que j'aurais du avoir deux incompatibles pointeurs pointant vers la même partie de la mémoire. Je pourrais alors naïvement code quelque chose comme ceci:

struct Msg
{
   unsigned int a;
   unsigned int b;
};

int main()
{
   // Get a 32-bit buffer from the system
   uint32_t* buff = malloc(sizeof(Msg));

   // Alias that buffer through message
   Msg* msg = (Msg*)(buff);

   // Send a bunch of messages    
   for (int i =0; i < 10; ++i)
   {
      msg->a = i;
      msg->b = i+1;
      SendWord(buff[0] );
      SendWord(buff[1] );   

   }

}

La stricte aliasing règle rend cette installation illégale, le déréférencement d'un pointeur qui définit un alias d'un incompatible type est un comportement indéfini. Malheureusement, vous pouvez toujours le code de cette façon, peut-être* obtenir quelques avertissements, il compile très bien, seul à avoir bizarre un comportement inattendu lorsque vous exécutez le code.

*(gcc semble assez incohérent dans sa capacité à donner de l'aliasing mises en garde, nous donnant un avertissement amical ici , mais pas ici)

Pour voir pourquoi ce comportement n'est pas défini, nous devons penser à ce que la stricte aliasing règle achète le compilateur. En fait, avec cette règle, il n'a pas à penser à insérer des instructions pour rafraîchir le contenu de buff à chaque exécution de la boucle. Au lieu de cela lors de l'optimisation, avec quelques fâcheusement inappliquée hypothèses sur la création d'alias, il peut omettre ces instructions, chargez buff[0] et buff[1] une fois avant la boucle est exécuté, et d'accélérer le corps de la boucle. Avant stricte de l'aliasing a été introduit, le compilateur a dû vivre dans un état de paranoïa que le contenu de buff peut changer à tout moment à partir de n'importe où par n'importe qui. Donc, pour obtenir un supplément de performances de pointe, et en supposant que la plupart des gens ne sont pas de type jeu de pointeurs, de la stricte aliasing règle a été introduite.

Gardez à l'esprit, si vous pensez que l'exemple est tiré par les cheveux, cela peut même se produire si vous êtes de passage d'un tampon à une autre fonction que de faire l'envoi pour vous, si vous avez.

 void SendMessage(uint32_t* buff, size_t size32)
 {
    for (int i = 0; i < size32; ++i) 
    {
       SendWord(buff[i]);
    }
 }

Et réécriture de notre boucle pour profiter de cette fonction pratique

   for (int i =0; i < 10; ++i)
   {
      msg->a = i;
      msg->b = i+1;
      SendMessage(buff, 2);
   }

Le compilateur peut ou peut ne pas être en mesure de ou assez intelligent pour essayer de inline SendMessage et il peut ou ne peut pas décider à charge ou à charge pas de buff de nouveau. Si SendMessage fait partie d'une autre API qui est compilé séparément, il a probablement des instructions pour le chargement buff du contenu. Puis à nouveau, peut-être que vous êtes en C++ et c'est en quelques basé sur un modèle d'en-tête uniquement de la mise en œuvre, que le compilateur croit en ligne. Ou peut-être que c'est juste quelque chose que vous avez écrit dans votre .fichier c pour votre propre convenance. De toute façon un comportement indéfini pourrait encore s'ensuivre. Même si nous savons que certains de ce qui se passe sous le capot, il est encore une violation de la règle donc pas de comportement bien déterminé est garanti. Donc, juste en les enveloppant dans une fonction qui prend notre mot délimité de la mémoire tampon n'est pas nécessairement de l'aide.

Alors, comment puis-je contourner ce problème?

  • L'utilisation d'un syndicat. La plupart des compilateurs soutenir ce sans se plaindre stricte de l'aliasing. Ceci est permis en C99 et explicitement autorisés dans C11.
 union {
 Msg msg;
 unsigned int asBuffer[sizeof(Msg)];
};
  • Vous pouvez désactiver stricte de l'aliasing dans votre compilateur (f[n-]strict-aliasing dans gcc))

  • Vous pouvez utiliser char* pour créer un alias à la place de votre système de mot. Les règles permettent une exception pour char*. Son toujours supposé que l' char* des alias d'autres types. Toutefois, cela ne fonctionne pas dans l'autre sens, il n'y a aucune hypothèse que votre struct alias un tampon de caractères.

Débutant méfiez-vous

Ce n'est qu'un potentiel de champ de mines lors de la superposition de deux types sur uns des autres. Renseignez-vous également sur endianess, mot d'alignement, et comment traiter les problèmes d'alignement par le biais de l'emballage des structs correctement.

232voto

Niall Points 2074

La meilleure explication que j'ai trouvé est par Mike Acton, la Compréhension Stricte de l'Aliasing. Il se concentre un peu sur PS3 développement, mais qui est fondamentalement juste GCC.

De l'article:

"Stricte de l'aliasing est une supposition faite par le C (ou C++) du compilateur, qui déréférencement de pointeurs vers des objets de types différents ne sera jamais reportez-vous à la même emplacement de mémoire (c'est à dire alias les uns des autres.)"

Donc, fondamentalement, si vous avez un int* de pointage pour certains de mémoire contenant et int et puis vous pointez float* pour que la mémoire et l'utiliser comme un float-vous de briser la règle. Si votre code ne respecte pas cela, alors le compilateur optimiseur sera plus susceptible de casser votre code.

L'exception à la règle est une char*, ce qui est permis à point à n'importe quel type.

132voto

Ben Voigt Points 151460

C'est la stricte aliasing règle dans la section 3.10 du C++03 standard (autres réponses fournir une bonne explication, mais aucun ne contenait de la règle elle-même):

Si un programme tente d'accéder à la valeur d'un objet à travers une lvalue d'autre que l'un des types suivants le comportement est indéfini:

  • le type dynamique de l'objet,
  • un cv qualifiés version du type dynamique de l'objet,
  • un type qui est signé ou non signé de type correspondant au type dynamique de l'objet,
  • un type qui est signé ou non signé de type correspondant à un cv qualifiés version du type dynamique de l'objet,
  • une agrégation ou une union de type qui comprend l'un des types mentionnés ci-dessus, parmi ses membres (y compris, de manière récursive, un membre d'un subaggregate ou contenus de l'union),
  • un type qui est une (peut-être de cv qualifiés) de la classe de base de type de le type dynamique de l'objet,
  • un char ou unsigned char type.

Nouveau C++11 libellé (les changements sont mis en avant):

Si un programme tente d'accéder à la valeur d'un objet par l'intermédiaire d'un glvalue d'autre que l'un des types suivants le comportement est indéfini:

  • le type dynamique de l'objet,
  • un cv qualifiés version du type dynamique de l'objet,
  • un type similaire (tel que défini dans la section 4.4) pour le type dynamique de l'objet,
  • un type qui est signé ou non signé de type correspondant au type dynamique de l'objet,
  • un type qui est signé ou non signé de type correspondant à un cv qualifiés version du type dynamique de l'objet,
  • une agrégation ou une union de type qui comprend l'un des types mentionnés ci-dessus, parmi ses éléments ou non des données membres statiques (y compris, de manière récursive, un élément ou un non-membre de données statiques d'un subaggregate ou contenus de l'union),
  • un type qui est une (peut-être de cv qualifiés) de la classe de base de type de le type dynamique de l'objet,
  • un char ou unsigned char type.

Deux changements ont été minimes: glvalue au lieu de lvalue, et à la clarification du total/union de cas.

Le troisième changement est une forte garantie (relâche le fort aliasing de la règle): Le nouveau concept de types similaires qui sont maintenant à l'abri de l'alias.


Aussi le C libellé (C99; ISO/IEC 9899:1999 6.5/7; exactement le même libellé est utilisé dans la norme ISO/IEC 9899:2011 §6.5 ¶7):

Un objet doit avoir sa valeur stockée et accessible uniquement par une lvalue expression qui est l'un des types suivants 73) ou 88):

  • un type compatible avec l'efficacité du type de l'objet,
  • une version qualifiée d'un type compatible avec l'effectif type de l'objet,
  • un type qui est signé ou non signé type correspondant à la efficace type de l'objet,
  • un type qui est signé ou non signé de type correspondant à une version qualifiée de l'effectif type de l'objet,
  • une agrégation ou une union de type qui comprend une desdites types parmi ses membres (y compris, de manière récursive, un membre d'un subaggregate ou contenus de l'union), ou
  • un type de caractère.

73) ou 88) Le but de cette liste est de préciser les circonstances dans lesquelles un objet peut ou peut ne pas être un alias.

44voto

Patrick Points 607

Stricte de l'aliasing ne fait pas référence seulement à des pointeurs, cela affecte les références ainsi, j'ai écrit un article à ce sujet pour le coup de pouce wiki des développeurs et il a été si bien reçu que j'en ai fait une page sur mon consultation du site web. Il explique ce qu'il est, pourquoi il confond tellement les gens et quoi faire à ce sujet. Stricte Aliasing Livre Blanc. En particulier, il explique pourquoi les syndicats sont des comportements à risque pour le C++, et pourquoi à l'aide de memcpy est la seule solution portable à travers à la fois le C et le C++. Espérons que cela est utile.

34voto

Ingo Blackman Points 433

Comme additif à ce que Doug T. l'a déjà écrit, ici est un simple cas de test qui déclenche probablement avec gcc :

de vérifier.c

#include <stdio.h>

void check(short *h,long *k)
{
    *h=5;
    *k=6;
    if (*h == 5)
        printf("strict aliasing problem\n");
}

int main(void)
{
    long      k[1];
    check((short *)k,k);
    return 0;
}

Compiler avec gcc -O2 -o check check.c. Généralement (avec la plupart des versions de gcc, j'ai essayé) ce sorties "strict problème d'aliasing", parce que le compilateur suppose que le "h" ne peut pas être la même adresse que le "k" dans la "case" de la fonction. Parce que le compilateur optimise l' if (*h == 5) loin et appelle toujours le printf.

Pour ceux qui sont intéressés voici le x64 code assembleur, produit par gcc 4.6.3, en cours d'exécution sur ubuntu 12.04.2 pour x64:

movw    $5, (%rdi)
movq    $6, (%rsi)
movl    $.LC0, %edi
jmp puts

Si la si la condition est complètement disparu du code assembleur.

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: