“Fonction virtuelle pure appelée” sur gcc 4.4 mais pas sur les versions plus récentes ni sur les versions 3.4

J’ai un MCVE qui, sur certaines de mes machines, se bloque lors de la compilation avec g ++ version 4.4.7 mais fonctionne avec clang ++ version 3.4.2 et g ++ version 6.3.

J’aimerais avoir de l’aide pour savoir s’il s’agit d’un comportement non défini ou d’un bogue réel de cette version ancienne de gcc.

Code

#include  class BaseType { public: BaseType() : _present( false ) {} virtual ~BaseType() {} virtual void clear() {} virtual void setSsortingng(const char* value, const char* fieldName) { _present = (*value != '\0'); } protected: virtual void setStrNoCheck(const char* value) = 0; protected: bool _present; }; // ---------------------------------------------------------------------------------- class TypeTextFix : public BaseType { public: virtual void clear() {} virtual void setSsortingng(const char* value, const char* fieldName) { clear(); BaseType::setSsortingng(value, fieldName); if( _present == false ) { return; // commenting this return fix the crash. Yes it does! } setStrNoCheck(value); } protected: virtual void setStrNoCheck(const char* value) {} }; // ---------------------------------------------------------------------------------- struct Wrapper { TypeTextFix _text; }; int main() { { Wrapper wrapped; wrapped._text.setSsortingng("123456789012", NULL); } // if I add a write to stdout here, it does not crash oO { Wrapper wrapped; wrapped._text.setSsortingng("123456789012", NULL); // without this line (or any one), the program runs just fine! } } 

Comstackr et exécuter

 g++ -O1 -Wall -Werror thebug.cpp && ./a.out pure virtual method called terminate called without an active exception Aborted (core dumped) 

C’est en fait minime, si on supprime une fonctionnalité de ce code, il s’exécute correctement.

Analyser

L’extrait de code fonctionne -O0 lorsqu’il est compilé avec -O0 . MAIS il fonctionne toujours -O0 +flag lorsqu’il est compilé avec l’ -O0 +flag pour chaque indicateur de -O1 défini dans la documentation GnuCC .

Un core dump est généré à partir duquel on peut extraire la trace:

 (gdb) bt #0 0x0000003f93e32625 in raise () from /lib64/libc.so.6 #1 0x0000003f93e33e05 in abort () from /lib64/libc.so.6 #2 0x0000003f98ebea7d in __gnu_cxx::__verbose_terminate_handler() () from /usr/lib64/libstdc++.so.6 #3 0x0000003f98ebcbd6 in ?? () from /usr/lib64/libstdc++.so.6 #4 0x0000003f98ebcc03 in std::terminate() () from /usr/lib64/libstdc++.so.6 #5 0x0000003f98ebd55f in __cxa_pure_virtual () from /usr/lib64/libstdc++.so.6 #6 0x00000000004007b6 in main () 

N’hésitez pas à demander des tests ou des détails dans les commentaires. A demandé:

  • Est-ce le code actuel ? Oui! il est! octet pour octet. J’ai vérifié et revérifié.

  • Quelle version exacte de GnuCC du vous utilisez?

     $ g++ --version g++ (GCC) 4.4.7 20120313 (Red Hat 4.4.7-16) Copyright (C) 2010 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
  • Peut-on voir l’assemblage généré? Oui, le voici sur pastebin.com

Il s’agit d’un bogue spécifique à Red Hat non présent dans FSF GCC. Ce n’est pas un problème dans votre code.

Sur un système doté à la fois de GCC CentOS 6 et de FSF GCC 4.4.7, ayant tous les deux généré une liste d’assemblages et visualisant les différences entre les deux, un bit saute:

Le GCC de CentOS 6 génère

 movq $_ZTV8BaseType+16, (%rsp) 

alors que la FSF GCC 4.4.7 génère

 movq $_ZTV11TypeTextFix+16, (%rsp) 

En d’autres termes, l’un des correctifs GCC de Red Hat lui permet de configurer la vtable de manière incorrecte. Cela fait partie de votre fonction main , vous pouvez le voir dans votre propre liste d’assemblages peu après .L48:

Red Hat applique de nombreux correctifs à sa version de GCC, et certains d’entre eux sont des correctifs qui affectent la génération de code. Malheureusement, l’un d’eux semble avoir un effet secondaire non voulu.

Bien que la vraie solution à ce bogue soit de ne pas utiliser RedHat GnuCC 4.4.7 (ni aucun compilateur RedHat …), nous sums temporairement coincés avec cette version.

Nous avons trouvé une alternative: obscurcir le constructeur de BaseType pour le compilateur, l’empêchant ainsi de trop l’optimiser. Nous l’avons fait simplement en définissant BaseType::BaseType() dans une unité de traduction distincte.

Cela évite le bogue g ++. Nous avons en effet vérifié que les pointeurs de table virtuelle BaseType et TypeTextFix étaient écrits dans un object construit avant d’appeler ses constructeurs associés.