c ++ linux double destruction de la variable statique. les symboles de liaison se chevauchent

Environnement: Linux x64, compilateur gcc 4.x

Le projet a la structure suivante:

static library "slib" -- inside this library, there is static object "sobj" dynamic library "dlib" -- links statically "slib" executable "exe": -- links "slib" statically -- links "dlib" dynamically 

à la fin du programme, “sobj” est détruit deux fois. Ce comportement est attendu, MAIS il est détruit deux fois à la même adresse mémoire, c’est-à-dire le même “this” dans le destructeur – le résultat est un problème de double destruction. Je pense que cela est dû à un chevauchement de symboles.

Quelle solution pour ce conflit? Peut-être une option de liaison?


Voici un cas de test:


main_exe.cpp

 #include  #include "static_lib.h" #include "dynamic_lib.h" int main(int argc, char *argv[]) { stat_useStatic(); din_useStatic(); return EXIT_SUCCESS; } 

static_lib.h

 #ifndef STATIC_LIB_H #define STATIC_LIB_H #include  void stat_useStatic(); struct CTest { CTest(): status(isAlive) { printf("CTest() this=%d\n",this); } ~CTest() { printf("~CTest() this=%d, %s\n",this,status==isAlive?"is Alive":"is Dead"); status=isDead; } void use() { printf("use\n"); } static const int isAlive=12385423; static const int isDead=6543421; int status; static CTest test; }; #endif 

static_lib.cpp

 #include "static_lib.h" CTest CTest::test; void stat_useStatic() { CTest::test.use(); } 

dynamic_lib.h

 #ifndef DYNAMIC_LIB_H #define DYNAMIC_LIB_H #include "static_lib.h" #ifdef WIN32 #define DLLExport __declspec(dllexport) #else #define DLLExport #endif DLLExport void din_useStatic(); #endif 

dynamic_lib.cpp

 #include "dynamic_lib.h" DLLExport void din_useStatic() { CTest::test.use(); } 

CMakeLists.txt

 project( StaticProblem ) cmake_minimum_required(VERSION 2.6) if(WIN32) else(WIN32) ADD_DEFINITIONS(-fPIC) endif(WIN32) ADD_LIBRARY( static_lib STATIC static_lib.cpp static_lib.h) ADD_LIBRARY( dynamic_lib SHARED dynamic_lib.cpp dynamic_lib.h) TARGET_LINK_LIBRARIES( dynamic_lib static_lib ) ADD_EXECUTABLE( main_exe main_exe.cpp ) TARGET_LINK_LIBRARIES( main_exe static_lib dynamic_lib ) 

Cet exemple fonctionne correctement, sous Windows, mais sous Linux – il y a un problème. Comme cela fonctionne bien sur Windows, la solution devrait être comme changer une option de liaison ou quelque chose comme ça, mais pas changer la structure du projet ou ne pas utiliser les vars statiques.

Sortie:

les fenêtres

 CTest() this=268472624 CTest() this=4231488 use use ~CTest() this=4231488, is Alive ~CTest() this=268472624, is Alive 

Linux

 CTest() this=6296204 CTest() this=6296204 use use ~CTest() this=6296204, is Alive ~CTest() this=6296204, is Dead 

OK, j’ai trouvé la solution:

http://gcc.gnu.org/wiki/Visibility

Par exemple si change

 static CTest test; 

à

 __atsortingbute__ ((visibility ("hidden"))) static CTest test; 

le problème disparaîtra. Linux:

 CTest() this=-1646158468 CTest() this=6296196 use use ~CTest() this=6296196, is Alive ~CTest() this=-1646158468, is Alive 

La sortie nm avant correction était:

 0000000000200dd4 B _ZN5CTest4testE 

après correction:

 0000000000200d7c b _ZN5CTest4testE 

La différence est changée par le symbole global “B” en symbole local “b”.

Au lieu d’append ” atsortingbut ” (visibilité (“caché”))) “aux symboles, il est possible d’utiliser l’option du compilateur” -fvisibility = hidden “. Cette option fait que gcc se comporte beaucoup plus comme Windows env.

TL; DR: vous ne devez pas lier une bibliothèque une seule fois en tant que dépendance statique et une fois en tant que dépendance dynamic.


Comment les destructeurs de variables statiques sont-ils exécutés dans l’ABI Itanium (utilisé par clang, gcc, icc …)?

La bibliothèque standard C ++ offre une fonctionnalité standard pour planifier l’exécution d’une fonction lors de l’arrêt du programme ( après la fin du programme principal) au format atexit .

Le comportement est relativement simple, atexit construit essentiellement une stack de rappels et les exécute donc dans l’ordre inverse de leur planification.

Chaque fois qu’une variable statique est construite, immédiatement après la fin de sa construction, un rappel est enregistré dans la stack atexit pour le détruire lors de l’arrêt.


Que se passe-t-il lorsqu’une variable statique existe à la fois dans une bibliothèque liée de manière statique et dans une bibliothèque liée de manière dynamic?

Il tente d’exister deux fois.

Chaque bibliothèque aura:

  • une zone mémoire réservée à la variable, désignée par le symbole correspondant (le nom mutilé de la variable),
  • une entrée dans la section load pour créer la variable et planifier sa destruction.

La surprise vient de la façon dont la résolution des symboles fonctionne dans le chargeur. Essentiellement, le chargeur établit un mappage entre le symbole et l’emplacement (pointeur), selon le principe du premier arrivé, premier servi.

Cependant, les sections de chargement / déchargement ne portent pas de nom et, par conséquent, chacune d’elles est exécutée intégralement.

Donc:

  • la variable statique est construite une première fois,
  • la variable statique est construite une seconde fois sur la première (qui fuit),
  • la variable statique est détruite une première fois,
  • la variable statique est détruite une seconde fois; qui est généralement l’endroit où le problème est détecté.

Et alors?

La solution est simple: ne pas lier à la fois une bibliothèque statique A (directement) et une bibliothèque dynamic B également liée à A (dynamicment ou statiquement).

Selon le cas d’utilisation, vous pouvez soit:

  • relier statiquement contre B,
  • lier dynamicment contre A et B.

Comme cela fonctionne correctement sur Windows, la solution devrait être comme changer une option de liaison ou quelque chose comme ça, mais pas changer la structure du projet ou ne pas utiliser les vars statiques.

Dans le cas peu probable où vous auriez réellement besoin de deux instances indépendantes de la variable statique, à part le refactoring de votre code, il est possible de masquer les symboles dans votre bibliothèque dynamic.

Le comportement par défaut de Windows, qui est la raison pour laquelle l’atsortingbut DLLExport est requirejs là-bas, et pourquoi depuis qu’il a été oublié pour CTest::test le comportement sous Windows est différent.

Notez toutefois que tout futur responsable de ce projet vous maudira fortement si vous optez pour ce comportement. Personne ne s’attend à ce qu’une variable statique ait plusieurs instances.

Au fait, si définir de la variable statique dans la fonction stat_useStatic, ce ne sera qu’une instance de cette variable statique dans le programme entier de linux (mais deux instances de Windows) – et nous utilisons cette solution pour résoudre ce problème. Voici les changements

 void stat_useStatic() { static CTest stest; stest.use(); CTest::test.use(); } DLLExport void din_useStatic() { stat_useStatic(); CTest::test.use(); } 

Maintenant, le comportement de Linux et de Windows diffère encore plus:

les fenêtres

 CTest() this=268476728 CTest() this=4235592 CTest() this=4235584 use use CTest() this=268476720 use use use ~CTest() this=4235584, is Alive ~CTest() this=4235592, is Alive ~CTest() this=268476720, is Alive ~CTest() this=268476728, is Alive 

Linux

 CTest() this=6296376 CTest() this=6296376 CTest() this=6296392 use use use use use ~CTest() this=6296392, is Alive ~CTest() this=6296376, is Alive ~CTest() this=6296376, is Dead 

Comme vous pouvez le voir, linux ne crée qu’une seule variable statique, mais Windows crée deux instances.

En fait, il semblerait que linux ne devrait pas créer en double et détruire la variable statique dans le premier cas, par sa logique, comme dans le second cas (static var dans func).

L’utilisation de la fonction statique locale var au lieu de la classe statique est juste une solution de contournement, pas une solution réelle. Parce que la source de la bibliothèque peut être indisponible.

Difficile à dire sans voir aucun code, mais ce territoire (bibliothèques chargées dynamicment) n’est en effet pas explicitement couvert par la norme, il est donc possible que différentes implémentations gèrent différemment les observations secondaires.

Ne pouvez-vous pas simplement éviter cette confusion, par exemple en utilisant différents espaces de noms pour les deux instances de la bibliothèque statique (par exemple en créant un espace de noms à utiliser pour l’object statique défini par une option de ligne de commande)?