27 votes

Pourquoi GCC est-il amené à autoriser un comportement non défini simplement en le mettant en boucle?

Ce qui suit est absurde encore compile proprement avec g++ -Wall -Wextra -Werror -Winit-self (j'ai testé GCC 4.7.2 et 4.9.0):

#include <iostream>
#include <string>

int main()
{
  for (int ii = 0; ii < 1; ++ii)
  {
    const std::string& str = str; // !!
    std::cout << str << std::endl;
  }
}

La ligne marquée !! entraîne un comportement non défini, mais n'est pas diagnostiquée par GCC. Toutefois, en commentant l' for ligne la GCC se plaindre:

error: ‘str' is used uninitialized in this function [-Werror=uninitialized]

Je voudrais savoir: pourquoi est-GCC si facilement berner ici? Lorsque le code n'est pas dans une boucle, GCC sait que c'est faux. Mais mettre le même code dans une boucle simple et GCC ne plus comprendre. Ce qui me dérange, car nous comptons beaucoup sur le compilateur de nous informer lorsque nous faisons des erreurs stupides en C++, mais il échoue pour une apparence cas trivial.

Bonus trivia:

  • Si vous modifiez std::string de int et tourner sur l'optimisation, la GCC devra en diagnostiquer l'erreur, même avec la boucle.
  • Si vous générez le code cassé avec -O3, GCC littéralement appelle la ostream insérer une fonction avec un pointeur null pour l'argument chaîne. Si vous pensiez que vous étiez à l'abri de références null si vous n'avez pas à faire des casting, détrompez-vous.

J'ai déposé un bug de GCC pour cela: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63203 - je voudrais encore pour obtenir une meilleure compréhension de ce qui s'est passé et comment il peut avoir un impact sur la fiabilité des diagnostics similaires.

11voto

Jonathan Wakely Points 45593

Je voudrais encore pour obtenir une meilleure compréhension de ce qui s'est passé et comment il peut avoir un impact sur la fiabilité des diagnostics similaires.

À la différence de Bruit, GCC n'a pas de logique pour détecter les auto-initialisé références, afin d'obtenir un avertissement ici s'appuie sur le code pour la détection de l'utilisation de variables non initialisées, ce qui est assez capricieux et peu fiables (voir Mieux non Initialisée mises en garde pour la discussion).

Avec un int le compilateur peut comprendre que vous écrivez "non initialisé" int pour le flux, mais avec un std::string il y a apparemment de trop nombreuses couches d'abstraction entre une expression de type std::string et obtenir le const char* il contient, et GCC ne parvient pas à détecter le problème.

par exemple, GCC n'donner un avertissement pour un exemple plus simple, avec moins de code entre la déclaration et l'utilisation de la variable, aussi longtemps que vous permettre à certains d'optimisation:

extern "C" int printf(const char*, ...);

struct string {
  string() : data(99) { }
  int data;
  void print() const { printf("%d\n", data); }
};

int main()
{
  for (int ii = 0; ii < 1; ++ii)
  {
    const string& str = str; // !!
    str.print();
  }
}

d.cc: In function ‘int main()':
d.cc:6:43: warning: ‘str' is used uninitialized in this function [-Wuninitialized]
   void print() const { printf("%d\n", data); }
                                           ^
d.cc:13:19: note: ‘str' was declared here
     const string& str = str; // !!
                   ^

Je soupçonne ce genre de manque de diagnostic n'est susceptible d'affecter une poignée de diagnostic qui s'appuient sur des heuristiques pour détecter les problèmes. Celles-ci seraient ceux qui donnent un avertissement de la forme "peut être utilisé non initialisé" ou "peut violer stricte aliasing règles", et sans doute les "indice de tableau est le tableau ci-dessus les limites d'avertissement". Ces avertissements ne sont pas précis à 100% et "compliqué" logique comme les boucles(!) peut causer le compilateur de renoncer à analyser le code et ne parviennent pas à donner un diagnostic.

À mon humble avis, la solution serait d'ajouter de vérification pour l'auto-initialisé références au moment de l'initialisation, et ne pas compter sur la détection il est non initialisée plus tard, quand il est utilisé.

2voto

BadZen Points 721

Vous prétendez que c'est un comportement indéfini, mais quand je compile les deux cas, à l'assemblée, je vois vraiment la fonction d'étendue variable n'étant pas initialisé sur la pile, et le bloc d'étendue variable à NULL.

C'est autant de réponse que vous obtenez à partir de moi. J'ai téléchargé le C++ spec pour définitivement régler cela, mais est tombé dans un Lovecraftian-type de fugue quand je pose les yeux sur elle, afin de préserver ma santé mentale fragile...

Je soupçonne fortement le bloc dont l'étendue est le cas, n'est pas réellement défini.

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