web-dev-qa-db-fra.com

gcc, alias strict et histoires d'horreur

Dans gcc-strict-aliasing-and-casting-through-a-union j'ai demandé si quelqu'un avait rencontré des problèmes avec la punition d'union via des pointeurs. Jusqu'à présent, la réponse semble être Non.

Cette question est plus large: avez-vous n'importe quoi des histoires d'horreur sur gcc et l'alias strict?

Contexte: Citant de la réponse d'AndreyT dans c99-strict-aliasing-rules-in-c-gcc :

"Les règles d'aliasing strictes 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 d'accéder à un objet d'un type via une valeur l d'un autre type est présente dans C89/90 (6.3 ) ainsi qu'en C++ 98 (3.10/15). ... C'est juste que tous les compilateurs n'ont pas voulu (ou osé) l'appliquer ou s'y fier. "

Eh bien, gcc ose maintenant le faire, avec son -fstrict-aliasing commutateur. Et cela a causé quelques problèmes. Voir, par exemple, l'excellent article http://davmac.wordpress.com/2009/10/ sur un bug Mysql, et l'excellente discussion 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 à vous? Problèmes pas indiqué par -Wstrict-aliasing serait, bien entendu, préférable. Et d'autres compilateurs C sont également les bienvenus.

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

La 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 l'alias -fno-strict, l'ordre d'écriture et de memcpy est inversé (ce qui signifie qu'un faux len est copié dans le flux).

Code compilé, utilisant gcc 4.3.4 sur CYGWIN avec -O3 (veuillez 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 donnera généralement (toujours?) un avertissement. Mais je croyez une solution valide à cela (pour gcc ) est d'utiliser:

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

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

53
Joseph Quinsey

Aucune histoire d'horreur à moi, mais voici quelques citations de Linus Torvalds (désolé si elles figurent déjà dans l'une des références liées dans la question):

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

Date mer. 26 févr. 2003 09:22:15 -0800 Objet Re: Compilation invalide sans -fno-strict-aliasing De Jean Tourrilhes <>

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

Jean Tourrilhes <> a déclaré:

Cela ressemble à un bogue du compilateur ... Certains utilisateurs se sont plaints que lorsque le code suivant est compilé sans l'alias -fno-strict, l'ordre de l'écriture et de memcpy est inversé (ce qui signifie qu'un faux len est copié par mem) dans le flux). Code (depuis 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;
}

À mon humble avis, le compilateur doit avoir suffisamment de contexte pour savoir que la réorganisation est dangereuse. Toute suggestion visant à 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 n'est pas le problème dont je me plains.

(Remarquez avec du recul: ce code est très bien, mais l'implémentation Linux de memcpyétait une macro qui a été castée en long * pour copier en plus gros morceaux. Avec une définition correcte memcpy, gcc -fstrict-aliasing n'est pas autorisé à casser ce code. Mais cela signifie que vous avez besoin d'un asm en ligne pour définir un noyau memcpy si votre compilateur ne sait pas comment transformer un octet -copie en boucle en asm efficace, ce qui était le cas pour gcc avant gcc7)

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

Jean Tourrilhes a écrit:>

Cela ressemble à un bug de compilation pour moi ...

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

Les gens de gcc sont plus intéressés à essayer de savoir ce qui peut être autorisé par les spécifications c99 qu'à faire fonctionner les choses . Le code d'alias en particulier ne vaut même pas la peine d'être activé, il n'est tout simplement pas possible de dire à gcc quand certaines choses peuvent être alias.

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

Le "problème" est que nous insérons la memcpy (), à quel point gcc ne se souciera pas du fait qu'il peut alias, donc ils réorganiseront tout et prétendront que c'est de sa faute. Même s'il n'y a aucun moyen sensé pour nous d'en parler à gcc.

J'ai essayé de faire preuve de bon sens il y a quelques années, et les développeurs de gcc se moquaient vraiment du monde réel dans ce domaine. Je serais surpris si cela 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/[email protected]/msg01647.html :

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

...

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

unsigned long a;

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

pourrait être réorganisé pour le mettre à 4 en premier (parce qu'ils ne sont clairement pas alias - en lisant la norme), puis parce que maintenant l'affectation de 'a = 5' était plus tard, l'affectation de 4 pourrait être complètement élidée! Et si quelqu'un se plaint que le compilateur est fou, les gens du compilateur diraient "nyaah, nyaah, les gens ont dit que nous pouvons le faire", sans aucune introspection pour demander s'il a fait un SENSE.

31
Michael Burr

SWIG génère du code qui dépend 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;
}
7
paleozogt

tableaux gcc, aliasing et 2-D de longueur variable: L'exemple de code suivant copie 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 c'est un bug ou une fonctionnalité. Je ne peux pas dupliquer le problème avec gcc 4.3.4 sur Cygwin, il a donc pu être corrigé. Quelques solutions:

  • Utilisez __attribute__((noinline)) pour copier ().
  • Utilisez le commutateur gcc -fno-strict-aliasing.
  • Modifiez le troisième paramètre de copy () de b[][n] En b[][2].
  • N'utilisez pas -O2 Ou -O3.

Notes complémentaires:

  • C'est une réponse, après un an et un jour, à ma propre question (et je suis un peu surpris qu'il n'y ait que deux autres réponses).
  • J'ai perdu plusieurs heures avec cela sur mon code actuel, un filtre de Kalman. Des modifications apparemment mineures auraient des effets drastiques, peut-être en raison de la modification de l'incrustation automatique de gcc (c'est une supposition; je ne suis toujours pas certain). Mais il ne s'agit probablement pas d'un histoire d'horreur.
  • Oui, je sais que vous n'écririez pas copy() comme ça. (Et, en passant, j'ai été légèrement surpris de voir que gcc n'a pas déroulé la double boucle.)
  • Aucun commutateur d'avertissement gcc, y compris -Wstrict-aliasing=, N'a fait quoi que ce soit ici.
  • Les tableaux 1D de longueur variable semblent être OK.

Mise à jour : Ce qui précède ne répond pas vraiment à la question du PO, car il (c'est-à-dire moi) posait des questions sur les cas où un alias strict "légitimement" a cassé votre code, alors que ce qui précède semble juste être un bogue de compilateur de jardin.

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

Et j'ai un exemple un peu plus simple 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 ce soit la combinaison -fstrict-aliasing Avec -finline Qui cause le bogue.

5
Joseph Quinsey

voici le mien:

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

cela a provoqué le dessin incorrect de certaines formes d'un programme CAD CAD. Dieu merci, les chefs de projet ont travaillé sur 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. puis uniquement avec -O2 activé. -fno-strict-aliasing l'a résolu.

2
don bright

La règle de séquence initiale commune de C était interprétée comme permettant d'écrire une fonction qui pourrait fonctionner sur la partie principale d'une grande variété de types de structure, à condition qu'ils commencent avec des éléments de types correspondants. Sous C99, la règle a été modifiée afin qu'elle ne s'applique que si les types de structure impliqués étaient membres de la même union dont la déclaration complete était visible au point d'utilisation.

Les auteurs de gcc insistent sur le fait que la langue en question n'est applicable que si les accès sont effectués via le type d'union, nonobstant le fait que:

  1. Il n'y aurait aucune raison de spécifier que la déclaration complete doit être visible si les accès doivent être effectués via le type d'union.

  2. Bien que la règle de la CEI ait été décrite en termes d'unions, son utilité première résidait dans ce qu'elle impliquait dans la manière dont les structures étaient présentées et accessibles. Si S1 et S2 étaient des structures partageant un CIS, il n'y aurait aucun moyen qu'une fonction qui accepte un pointeur vers un S1 et un S2 d'une source externe puisse se conformer aux règles CIS de C89 sans permettre au même comportement d'être utile avec des pointeurs vers structures qui n'étaient pas réellement à l'intérieur d'un objet d'union; spécifier le soutien de la CEI aux structures aurait donc été superflu étant donné qu'il était déjà spécifié pour les syndicats.

2
supercat

Le code suivant renvoie 10, sous gcc 4.4.4. Quelque chose ne va pas avec la méthode union ou gcc 4.4.4?

int main()
{
  int v = 10;

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

  s->v = 1;

  return v;
}
2
user470617