63 votes

gcc, strict-aliasing, et histoires d'horreur

Sur gcc-strict-aliasing-and-casting-through-a-union J'ai demandé si quelqu'un avait rencontré des problèmes avec le contournement des unions par des pointeurs. Jusqu'à présent, la réponse semble être Non .

Cette question est plus large : Avez-vous tout des histoires d'horreur sur gcc et l'aliasing strict ?

Historique : Citation de La réponse d'AndreyT dans c99-strict-aliasing-rules-in-c-gcc :

"Les règles strictes d'aliasing sont enracinées dans des parties de la norme qui étaient présentes en C et C++ depuis le début des temps [normalisés]. La clause qui interdit l'accès à un objet d'un type par une lvalue d'un autre type est présente dans C89/90 (6.3) ainsi que dans C++98 (3.10/15). ... C'est juste que tous les compilateurs n'ont pas voulu (ou osé) l'appliquer ou s'y fier. "

Bien, gcc ose aujourd'hui le faire, avec ses -fstrict-aliasing l'interrupteur. Et cela a causé quelques problèmes. Voir, par exemple, l'excellent article http://davmac.wordpress.com/2009/10/ sur un bug de Mysql, et la discussion tout aussi excellente dans http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html .

Quelques autres liens moins pertinents :

Donc, pour répéter, avez-vous une histoire d'horreur à raconter ? Problèmes no indiqué par -Wstrict-aliasing serait, bien sûr, préférable. Les autres compilateurs C sont également les bienvenus.

Ajouté le 2 juin : Le premier lien dans Réponse de Michael Burr qui fait en effet qualifié d'histoire d'horreur, est peut-être un peu daté (de 2003). J'ai fait un test rapide, mais le problème a apparemment disparu.

Source :

#include <string.h>
struct iw_event {               /* dummy! */
    int len;
};
char *iwe_stream_add_event(
    char *stream,               /* Stream of events */
    char *ends,                 /* End of stream */
    struct iw_event *iwe,       /* Payload */
    int event_len)              /* Real size of payload */
{
    /* Check if it's possible */
    if ((stream + event_len) < ends) {
            iwe->len = event_len;
            memcpy(stream, (char *) iwe, event_len);
            stream += event_len;
    }
    return stream;
}

La plainte spécifique est :

Certains utilisateurs se sont plaints que lorsque le code [ci-dessus] est compilé sans le -fno-strict-aliasing, l'ordre de l'écriture et du memcpy est inversé (ce qui signifie qu'un faux len est mem-copié dans le flux).

Code compilé, utilisant gcc 4.3.4 sur CYGWIN avec -O3 (merci de me corriger si je me trompe - mon assembleur est un peu rouillé !)

_iwe_stream_add_event:
        pushl       %ebp
        movl        %esp, %ebp
        pushl       %ebx
        subl        $20, %esp
        movl        8(%ebp), %eax       # stream    --> %eax
        movl        20(%ebp), %edx      # event_len --> %edx
        leal        (%eax,%edx), %ebx   # sum       --> %ebx
        cmpl        12(%ebp), %ebx      # compare sum with ends
        jae L2
        movl        16(%ebp), %ecx      # iwe       --> %ecx
        movl        %edx, (%ecx)        # event_len --> iwe->len (!!)
        movl        %edx, 8(%esp)       # event_len --> stack
        movl        %ecx, 4(%esp)       # iwe       --> stack
        movl        %eax, (%esp)        # stream    --> stack
        call        _memcpy
        movl        %ebx, %eax          # sum       --> retval
L2:
        addl        $20, %esp
        popl        %ebx
        leave
        ret

Et pour le deuxième lien dans la réponse de Michael,

*(unsigned short *)&a = 4;

gcc donne généralement (toujours ?) un avertissement. Mais je croire une solution valable à ce problème (pour gcc ) est à utiliser :

#define CAST(type, x) (((union {typeof(x) src; type dst;}*)&(x))->dst)
// ...
CAST(unsigned short, a) = 4;

J'ai demandé à SO si c'était possible dans les cas suivants gcc-strict-aliasing-and-casting-through-a-union mais jusqu'à présent, personne n'est en désaccord.

0 votes

0 votes

Réponse du caf stackoverflow.com/questions/1926282/ est une autre référence, mais elle n'est que théorique, pas horrible.

1 votes

En outre, pour être clair, les références à gcc les bogues de codage ne sont pas utiles - tout code a des bogues. Mais design Les "bugs" peuvent être critiqués.

37voto

Michael Burr Points 181287

Je n'ai pas d'histoire d'horreur à raconter, mais voici quelques citations de Linus Torvalds (désolé si elles se trouvent déjà dans l'une des références indiquées dans la question) :

http://lkml.org/lkml/2003/2/26/158 :

Date Wed, 26 Feb 2003 09:22:15 -0800 Sujet Re : Compilation invalide sans -fno-strict-aliasing De Jean Tourrilhes <>

Le mercredi 26 février 2003 à 04:38:10PM +0100, Horst von Brand a écrit :

Jean Tourrilhes <> a dit :

Cela ressemble à un bug du compilateur pour moi... Certains utilisateurs se sont plaints que lorsque le code suivant est compilé sans l'option -fno-strict-aliasing, l'ordre de l'écriture et du et du memcpy est inversé (ce qui signifie qu'un len fictif est mem-copié dans le stream). Code (de linux/include/net/iw_handler.h) :

static inline char *
iwe_stream_add_event(char *   stream,     /* Stream of events */
                     char *   ends,       /* End of stream */
                    struct iw_event *iwe, /* Payload */
                     int      event_len)  /* Real size of payload */
{
  /* Check if it's possible */
  if((stream + event_len) < ends) {
      iwe->len = event_len;
      memcpy(stream, (char *) iwe, event_len);
      stream += event_len;
  }
  return stream;
}

IMHO, le compilateur devrait disposer d'un contexte suffisant pour savoir que l'option réordonnancement est dangereux. Toute suggestion pour rendre ce code simple plus à l'épreuve des balles est la bienvenue.

Le compilateur est libre de supposer que char *stream et struct iw_event *iwe pointent. vers des zones de mémoire distinctes, en raison d'un aliasing strict.

Ce qui est vrai et ce qui n'est pas le problème dont je me plains.

(Note avec du recul : ce code est bon, mais l'implémentation de Linux de memcpy était une macro qui se transforme en long * à copier en plus gros morceaux. Avec un memcpy , gcc -fstrict-aliasing n'est pas autorisé à briser ce code. Mais cela signifie que vous avez besoin d'une asm en ligne pour définir un noyau memcpy si votre compilateur ne sait pas comment transformer une boucle de copie d'octets en asm efficace, ce qui était le cas de gcc avant gcc7)

Et le commentaire de Linus Torvald sur ce qui précède :

Jean Tourrilhes a écrit : >

Cela ressemble à un bug du compilateur pour moi...

Pourquoi pensez-vous que le noyau utilise "-fno-strict-aliasing" ?

Les gens de gcc sont plus intéressés à essayer de trouver ce qui peut être autorisé par les spécifications de c99 que de faire des choses réellement travail . Le site code d'aliasing en particulier ne vaut même pas la peine d'être activé, il n'est simplement pas possible d'indiquer à gcc quand certaines choses peuvent être aliasées.

Certains utilisateurs se sont plaints que lorsque le code suivant est compilé sans l'option -fno-strict-aliasing, l'ordre de l'écriture et du memcpy est inversé (ce qui signifie qu'un len fictif est copié en mémoire dans le flux).

Le "problème" est que nous mettons en ligne le memcpy(), et à ce moment-là, gcc ne se ne se soucie pas du fait qu'il peut aliaser, donc ils vont juste réordonner tout et prétendre que c'est de sa faute. Même s'il n'y a aucun moyen sensé moyen pour nous d'en parler à Gcc.

J'ai essayé d'obtenir un moyen sain il y a quelques années, et les développeurs de gcc ont réellement ne se souciaient pas du monde réel dans ce domaine. Je serais surpris si avait changé, à en juger par les réponses que j'ai déjà vues.

Je ne vais pas prendre la peine de le combattre.

Linus

http://www.mail-archive.com/linux-btrfs@vger.kernel.org/msg01647.html :

L'aliasing basé sur le type est stupide . C'est si incroyablement stupide que ce n'est même pas drôle. C'est cassé. Et gcc a pris la notion cassée, et l'a rendue encore plus cassante en en faisant une chose "par la lettre de la loi" qui n'a aucun sens.

...

Je sais pour un fait que gcc réordonnait les accès en écriture qui étaient clairement (statiquement) à la même adresse. Gcc pensait soudainement que

unsigned long a;

a = 5;
*(unsigned short *)&a = 4;

pourrait être réorganisé pour qu'il prenne d'abord la valeur 4 (parce qu'il est clair qu'il n'y a pas d'alias - en lisant la norme), et ensuite, parce que l'assignation de 'a=5' est postérieure, l'assignation de 4 pourrait être entièrement supprimée ! Et si quelqu'un se plaint que le compilateur est fou, les gens du compilateur diront "nyaah, nyaah, les gens de la norme ont dit que nous pouvons faire cela", sans aucune introspection pour demander si cela a du SENS.

0 votes

+1. Mais gcc donnera généralement (espérons-le toujours ?) un avertissement dans cet exemple.

2 votes

Et un possible La solution à la plainte de Linus est : #define CAST(type, x) (((union {typeof(x) src; type dst;}*)&(x))->dst) suivi par CAST(unsigned short, a) = 4;

63 votes

IMO, Linus semble être celui qui est stupide ici.

8voto

paleozogt Points 2553

SWIG génère du code qui dépend de la désactivation de l'aliasing strict, ce qui peut provoquer toutes sortes de problèmes .

SWIGEXPORT jlong JNICALL Java_com_mylibJNI_make_1mystruct_1_1SWIG_12(
       JNIEnv *jenv, jclass jcls, jint jarg1, jint jarg2) {
  jlong jresult = 0 ;
  int arg1 ;
  int arg2 ;
  my_struct_t *result = 0 ;

  (void)jenv;
  (void)jcls;
  arg1 = (int)jarg1; 
  arg2 = (int)jarg2; 
  result = (my_struct_t *)make_my_struct(arg1,arg2);
  *(my_struct_t **)&jresult = result;              /* <<<< horror*/
  return jresult;
}

6voto

Joseph Quinsey Points 4450

gcc, aliasing, et 2-D de longueur variable tableaux: L'exemple de code suivant des copies d'une matrice 2x2:

#include <stdio.h>

static void copy(int n, int a[][n], int b[][n]) {
   int i, j;
   for (i = 0; i < 2; i++)    // 'n' not used in this example
      for (j = 0; j < 2; j++) // 'n' hard-coded to 2 for simplicity
         b[i][j] = a[i][j];
}

int main(int argc, char *argv[]) {
   int a[2][2] = {{1, 2},{3, 4}};
   int b[2][2];
   copy(2, a, b);    
   printf("%d %d %d %d\n", b[0][0], b[0][1], b[1][0], b[1][1]);
   return 0;
}

Avec gcc 4.1.2 sur CentOS, j'obtiens:

$ gcc -O1 test.c && a.out
1 2 3 4
$ gcc -O2 test.c && a.out
10235717 -1075970308 -1075970456 11452404 (random)

Je ne sais pas si c'est généralement connu, et je ne sais pas si ce un bug ou une fonctionnalité. Je ne peux pas reproduire le problème avec gcc 4.3.4 sur Cygwin, donc il peut avoir été fixé. Certaines des solutions de rechange:

  • Utiliser __attribute__((noinline)) pour copier().
  • Utiliser gcc commutateur -fno-strict-aliasing.
  • Modifier le troisième paramètre de copie() à partir d' b[][n] de b[][2].
  • Ne pas utiliser -O2 ou -O3.

Note en outre:

  • C'est une réponse, après un an et un jour, à ma propre question (et je suis un peu surpris il y a seulement deux autres réponses).
  • J'ai perdu plusieurs heures avec cela sur mon propre code, un filtre de Kalman. Apparemment les petits changements pourraient avoir des effets dévastateurs, peut-être à cause de l'évolution du ccg automatique inline (c'est une supposition, je suis encore incertain). Mais il n'a probablement pas qualifier comme une histoire d'horreur.
  • Oui, je sais que vous ne voudriez pas écrire copy() aime cela. (Et, en aparté, j'ai été un peu surpris de voir la gcc n'a pas dérouler la double boucle.)
  • Pas de gcc avertissement commutateurs, comprennent -Wstrict-aliasing=, n'a rien ici.
  • 1-D de longueur variable tableaux semblent être OK.

Mise à jour: Le ci-dessus ne réponds pas vraiment à l'OP de la question, depuis qu'il a (c-I) a une question sur les cas où la stricte aliasing 'légitimement" a brisé votre code, alors que le ci-dessus semble juste être un jardin-variété compilateur bug.

Je l'ai signalé à GCC Bugzilla, mais ils n'étaient pas intéressés dans la vieille 4.1.2, même si (je crois) c'est la clé de la de 1 milliard de dollars RHEL5. Il ne se produit pas dans 4.2.4 jusqu'.

Et j'ai un peu plus simple exemple d'un bug similaire, avec une seule matrice. Le code:

static void zero(int n, int a[][n]) {
   int i, j;
   for (i = 0; i < n; i++)
   for (j = 0; j < n; j++)
      a[i][j] = 0;
}

int main(void) {
   int a[2][2] = {{1, 2},{3, 4}};
   zero(2, a);    
   printf("%d\n", a[1][1]);
   return 0;
}

produit les résultats:

gcc -O1 test.c && a.out
0
gcc -O1 -fstrict-aliasing test.c && a.out
4

Il semble que c'est la combinaison -fstrict-aliasing avec -finline qui provoque le bug.

5voto

don bright Points 323

Voici le mien :

http://forum.openscad.org/CGAL-3-6-1-causing-errors-but-CGAL-3-6-0-OK-tt2050.html

il a entraîné le dessin incorrect de certaines formes dans un programme de CAO. heureusement que les responsables du projet ont travaillé à la création d'une suite de tests de régression.

le bogue ne s'est manifesté que sur certaines plates-formes, avec des versions plus anciennes de GCC et des versions plus anciennes de certaines bibliothèques. et alors seulement avec -O2 activé. -fno-strict-aliasing l'a résolu.

0 votes

Avez-vous jamais trouvé la cause profonde ?

1 votes

Absolument pas, cela aurait pris des dizaines et des dizaines d'heures pour le retrouver - pour un cas de figure obscur lors de l'utilisation d'outils plus anciens. CGAL est une énorme bibliothèque C++ template hpp et le binaire peut prendre une heure à compiler.

0 votes

Ha oui je suis un peu familier avec ça, et votre caractérisation est 100% juste. J'aimerais bien savoir ce que c'est, cependant - l'aliasing strict, bien qu'il soit UB, semble ne causer que rarement des bugs réels.

1voto

user470617 Points 21

Le code suivant renvoie 10, sous gcc 4.4.4. Y a-t-il un problème avec la méthode union ou avec gcc 4.4.4 ?

int main()
{
  int v = 10;

  union vv {
    int v;
    short q;
  } *s = (union vv *)&v;

  s->v = 1;

  return v;
}

3 votes

Voir la documentation de gcc - gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Optimize-Options.html - concernant l'option -fstrict-aliasing. Elle indique spécifiquement que cette méthode n'est pas valide. Pour une ruse de type conforme, tous les accès à la valeur doivent se faire via le type union ; dans votre exemple, la valeur 10 n'est pas stockée via le type union.

0 votes

@davmac Qu'est-ce qu'un accès par un type ?

0 votes

@curiousguy par "accès par le type union" je voulais dire "accès par l'objet qui a le type union". Peut-être que "accès par l'objet d'union" serait une meilleure formulation. J'espère que cela clarifie la situation.

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