Vraag Statische variabele wordt tweemaal geïnitialiseerd


Overweeg ik heb een statische variabele in een compilatie-eenheid die in een statisch bibliotheek libA. Ik heb dan een andere compilatie-eenheid die toegang heeft tot deze variabele die in a eindigt gedeelde bibliotheek libB.so (dus libA moet worden gekoppeld aan libB). Ten slotte heb ik een hoofdfunctie die ook rechtstreeks toegang heeft tot de statische variabele van A en een afhankelijkheid hebben van libB (dus ik link naar libA en libB).

Ik merk vervolgens op dat de statische variabele twee keer wordt geïnitialiseerd, d.w.z. dat de constructor tweemaal wordt uitgevoerd! Dit lijkt niet te kloppen. Zou de linker beide variabelen niet moeten herkennen als hetzelfde en ze als één moeten optimaliseren?

Om mijn verwarring perfect te maken, zie ik dat het twee keer wordt uitgevoerd met hetzelfde adres! Dus misschien de linker deed herken het, maar deed niet verwijder de tweede aanroep in de static_initialization_and_destruction code?

Hier is een vitrine:

ClassA.hpp:

#ifndef CLASSA_HPP
#define CLASSA_HPP

class ClassA
{
public:
    ClassA();
    ~ClassA();
    static ClassA staticA;

    void test();
};

#endif // CLASSA_HPP

ClassA.cpp:

#include <cstdio>
#include "ClassA.hpp"

ClassA ClassA::staticA;

ClassA::ClassA()
{
    printf("ClassA::ClassA() this=%p\n", this);
}

ClassA::~ClassA()
{
    printf("ClassA::~ClassA() this=%p\n", this);
}

void ClassA::test()
{
    printf("ClassA::test() this=%p\n", this);
}

ClassB.hpp:

#ifndef CLASSB_HPP
#define CLASSB_HPP

class ClassB
{
public:
    ClassB();
    ~ClassB();

    void test();
};

#endif // CLASSB_HPP

ClassB.cpp:

 #include <cstdio>
 #include "ClassA.hpp"
 #include "ClassB.hpp"

 ClassB::ClassB()
 {
     printf("ClassB::ClassB() this=%p\n", this);
 }

 ClassB::~ClassB()
 {
     printf("ClassB::~ClassB() this=%p\n", this);
 }

 void ClassB::test()
 {
     printf("ClassB::test() this=%p\n", this);
     printf("ClassB::test: call staticA.test()\n");
     ClassA::staticA.test();
 }

test.cpp:

#include <cstdio>
#include "ClassA.hpp"
#include "ClassB.hpp"

int main(int argc, char * argv[])
{
    printf("main()\n");
    ClassA::staticA.test();
    ClassB b;
    b.test();
    printf("main: END\n");

    return 0;
}

Ik compileer en link als volgt:

g++ -c ClassA.cpp
ar rvs libA.a ClassA.o
g++ -c ClassB.cpp
g++ -shared -o libB.so ClassB.o libA.a
g++ -c Test.cpp
g++ -o test Test.cpp libA.a libB.so

Output is:

ClassA::ClassA() this=0x804a040
ClassA::ClassA() this=0x804a040
main()
ClassA::test() this=0x804a040
ClassB::ClassB() this=0xbfcb064f
ClassB::test() this=0xbfcb064f
ClassB::test: call staticA.test()
ClassA::test() this=0x804a040
main: END
ClassB::~ClassB() this=0xbfcb064f
ClassA::~ClassA() this=0x804a040
ClassA::~ClassA() this=0x804a040

Kan iemand uitleggen wat hier gebeurt? Wat doet de linker? Hoe kan het dezelfde variabele twee keer geïnitialiseerd?


10
2017-10-24 12:00


oorsprong


antwoorden:


Jij bent inclusief libA.a in libB.so. Door dit te doen, beide libB.so en libA.a bevatten ClassA.o, dat het statische lid definieert.

In de linkvolgorde die u hebt opgegeven, trekt de linker aan ClassA.o van de statische bibliotheek libA.a, dus ClassA.o initialisatiecode wordt eerder uitgevoerd main(). Wanneer de eerste functie in de dynamiek libB.so is toegankelijk, alle initializers voor libB.so worden uitgevoerd. Sinds libB.so omvat ClassA.o, ClassA.oDe statische initializer moet (opnieuw) worden uitgevoerd.

Mogelijke oplossingen:

  1. Zet ClassA.o niet in zowel libA.a als libB.so.

    g++ -shared -o libB.so ClassB.o
    
  2. Gebruik beide bibliotheken niet; libA.a is niet nodig.

    g++ -o test Test.cpp libB.so
    

Met een van de bovenstaande oplossingen wordt het probleem opgelost:

ClassA::ClassA() this=0x600e58
main()
ClassA::test() this=0x600e58
ClassB::ClassB() this=0x7fff1a69f0cf
ClassB::test() this=0x7fff1a69f0cf
ClassB::test: call staticA.test()
ClassA::test() this=0x600e58
main: END
ClassB::~ClassB() this=0x7fff1a69f0cf
ClassA::~ClassA() this=0x600e58

8
2017-10-25 22:52



Kan iemand uitleggen wat hier gebeurt?

Het is gecompliceerd.

Ten eerste, de manier waarop je je hoofdprogramma en de gedeelde bibliotheek hebt gekoppeld twee Instanties van staticA (en alle andere code van ClassA.cpp) aanwezig zijn: een in het hoofduitvoeringsprogramma en een in libB.so.

U kunt dit bevestigen door te draaien

nm -AD ./test ./libB.so | grep staticA

Het is dan ook niet zo verwonderlijk dat het ClassA constructor voor de twee instanties loopt twee keer, maar het is nog steeds verrassend dat de this pointer is hetzelfde (en komt overeen met staticA in het hoofdbestand).

Dat gebeurt omdat de runtime loader (niet succesvol) probeert om het gedrag van het linken met archiefbibliotheken te emuleren, en alle verwijzingen naar staticA naar het eerste wereldwijd geëxporteerde exemplaar dat het observeert (het exemplaar in test).

Dus wat kun je doen om dit op te lossen? Dat hangt ervan af wat staticA vertegenwoordigt eigenlijk.

Als het een soort singleton is, zou dat maar één keer in een programma moeten bestaan, dan is de eenvoudige oplossing ervoor zorgen dat er maar één exemplaar van is staticA. En een manier om dat te doen is om te eisen dat elk programma dat gebruikt libB.so ook links tegen libA.a, en niet link libB.so tegen libA.a. Dat zal het geval van elimineren sttaicA binnen libB.so. U hebt beweerd dat "libA moet worden gekoppeld aan libB", maar die bewering is onjuist.

Als alternatief, als je bouwt libA.so in plaats van libA.a, dan kun je koppelen libB.so tegen libA.so (zo libB.so is self-contained). Als de hoofdtoepassing ook links bevat libA.so, dat zou geen probleem zijn: er zal maar één exemplaar van zijn staticA binnen libA.so, het maakt niet uit hoe vaak die bibliotheek wordt gebruikt.

Aan de andere kant, als staticA staat voor een soort intern implementatiedetail en je bent ok omdat je er twee voorbeelden van hebt (zolang ze elkaar niet hinderen), dan is de oplossing om alles te markeren ClassA symbolen met verborgen zichtbaarheid, zoals dit antwoord suggereert.

Bijwerken:

waarom de linker het tweede exemplaar van staticA niet uit het uitvoerbare bestand verwijdert.

Omdat de linker doet wat je zei dat het moest doen. Als u uw link-opdrachtregel wijzigt in:

g++ -o test Test.cpp libB.so libA.a

dan zou de linker niet moeten linken ClassA in het hoofdbestand. Om te begrijpen waarom de volgorde van bibliotheken op de commandoregel van belang is, lees deze.


6
2017-10-25 20:29