Pytanie c ++ podwójne zniszczenie zmiennej statycznej. łączenie nakładających się symboli


Środowisko: linux x64, kompilator gcc 4.x

Projekt ma następującą strukturę:

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

pod koniec programu "sobj" jest dwukrotnie niszczony. Oczekuje się takiego zachowania, ALE jest on niszczony dwa razy pod tym samym adresem pamięci, tj. Ten sam "ten" w destruktorze - w wyniku tego powstaje problem podwójnego zniszczenia. Myślę, że wynika to z nakładania się pewnych symboli.

Jakie jest rozwiązanie tego konfliktu? Może jakaś opcja łączenia?


Oto przypadek testowy:


main_exe.cpp

#include <cstdlib>

#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 <cstdio>

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 )

Ten przykład działa OK, w systemie Windows, ale na Linuksie - jest problem. Ponieważ działa poprawnie w oknach, rozwiązanie powinno być jak zmiana opcji łączenia lub czegoś podobnego, ale nie zmieniać struktury projektu lub nie używać statycznych zmiennych.

Wydajność:

Windows

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

16
2017-07-15 23:02


pochodzenie


Jesteś pewnie nie tylko delete dwa wskaźniki do tego samego obiektu? Brzytwa Occam sugerowałaby, że to jest problem. - Chris Frederick
Czy możesz podać klasyczny "minimalny przykład kompilacji", który pokazuje problem? - Matteo Italia
Jestem w 100% pewien, że usuwasz go dwa razy - nigdy nie słyszałem o "nakładających się na siebie symbolach". Sprawdź swój kod. - Josh
Bez kodu możemy się tylko domyślać (angielski opis nigdy nie jest dokładny). Wytwórz kod i instrukcje do kompilacji, które demonstrują problem. - Martin York
Podałem przykład. Josh, wciąż jesteś w 100% pewny? - John


Odpowiedzi:


OK, znalazłem rozwiązanie:

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

Na przykład, jeśli zmienisz

static CTest test;

do

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

problem zniknie. Linux:

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

Wydajność nm przed naprawą:

0000000000200dd4 B _ZN5CTest4testE

po naprawie:

0000000000200d7c b _ZN5CTest4testE

Różnica zmienia globalny symbol "B" na lokalny symbol "b".

Zamiast dodawać "atrybut ((visibility ("hidden"))) "do symboli, możliwe jest użycie opcji kompilacji" -fvisibility = hidden ".Ta opcja powoduje, że gcc zachowuje się bardziej jak Windows env.


7
2017-08-13 21:40





TL; DR: nie powinieneś łączyć biblioteki raz jako statyczną zależność i raz jako dynamiczną zależność.


W jaki sposób destruktory zmiennych statycznych są wykonywane w Itanium ABI (używane przez clang, gcc, icc ...)?

Biblioteka Standardowa C ++ oferuje standardowe narzędzie do planowania realizacji funkcji podczas zamykania programu (po główny został zakończony) w formacie atexit.

Zachowanie jest względnie proste, atexit zasadniczo buduje stos wywołań zwrotnych i tym samym wykonuje je w rewers kolejność ich harmonogramowania.

Za każdym razem, gdy konstruowana jest zmienna statyczna, natychmiast po jej zakończeniu kończy się wywołanie zwrotne w atexit stos, aby go zniszczyć podczas wyłączania.


Co się dzieje, gdy istnieje zmienna statyczna obie w statycznie połączonej bibliotece i dynamicznie połączonej bibliotece?

To próbowanie istnieć dwa razy.

Każda biblioteka będzie miała:

  • obszar pamięci zarezerwowany dla zmiennej, wskazany przez odpowiedni symbol (zmasakrowana nazwa zmiennej),
  • wpis w sekcji load, aby zbudować zmienną i zaplanuj jej zniszczenie.

Zaskoczenie wynika ze sposobu, w jaki rozdzielczość symboli działa w module ładującym. Zasadniczo program ładujący tworzy mapowanie między symbolem a lokalizacją (wskaźnikiem) w oparciu o zasady "kto pierwszy ten lepszy".

Jednak sekcje load / unload są bezimienne, dlatego każdy z nich jest wykonywany w całości.

W związku z tym:

  • zmienna statyczna jest konstruowana po raz pierwszy,
  • zmienna statyczna jest konstruowana po raz drugi nad pierwszym (które wyciekło),
  • zmienna statyczna jest niszczona po raz pierwszy,
  • zmienna statyczna jest niszczona po raz drugi; na ogół tam, gdzie problem został wykryty.

Więc co?

Rozwiązanie jest proste: NIE łączyć z biblioteką statyczną A (bezpośrednio) i biblioteką dynamiczną B również łączącą się z A (dynamicznie lub statycznie).

W zależności od przypadku użycia możesz:

  • link statycznie przeciwko B,
  • link dynamicznie przeciwko zarówno A, jak i B.

Ponieważ działa poprawnie w systemie Windows, rozwiązanie powinno być jak zmiana opcji łączenia lub coś podobnego, ale nie zmieniać struktury projektu lub nie używać statycznych zmiennych.

W mało prawdopodobnym przypadku, gdy naprawdę potrzebujesz dwóch niezależnych instancji zmiennej statycznej, oprócz refaktoryzacji kodu, możesz ukryć symbole w bibliotece dynamicznej.

To domyślne zachowanie Windows, dlatego DLLExport atrybut jest tam wymagany i dlaczego, ponieważ został zapomniany CTest::test zachowanie w Windowsie jest inne.

Zauważ jednak, że każdy przyszły opiekun tego projektu przeklnie Cię głośno, jeśli wybierzesz takie zachowanie. Nikt nie oczekuje, że zmienna statyczna ma wiele instancji.


3
2018-05-13 15:46



Zdaję sobie sprawę, że jest to stary post, jednak jeśli szukasz najlepszego, jest to post, którego powinieneś poszukać, a nie ten, którego akceptuje jeden właściciel. Również, ten post ma kilka świetnych referencji, które pomogą lepiej zrozumieć ten problem. - fogo


Nawiasem mówiąc, jeśli zdefiniujemy static var wewnątrz funkcji stat_useStatic, będzie to tylko jedna instancja tego statycznego var w całym programie w Linuksie (ale dwie instancje w Windows) - i to właśnie używamy do obejścia tego problemu. Oto zmiany

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


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

Teraz zachowanie Linuksa i Windows różni się jeszcze bardziej:

Windows

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

Jak widać, Linux tworzy tylko jeden statyczny var, ale okna tworzą dwie instancje.

Naprawdę, wygląda na to, że Linux nie powinien podwójnie tworzyć i podwójnie niszczać statycznego var w pierwszym przypadku, według jego logiki, tak samo jak w drugim przypadku (static var inside func).

Używanie funkcji lokalne statyczne zmienne zamiast klas statycznych to tylko obejście, a nie rzeczywiste rozwiązanie. Ponieważ źródło biblioteki może być niedostępne.


2
2017-07-16 13:17





Trudno powiedzieć, nie widząc żadnego kodu, ale to terytorium (dynamicznie ładowane biblioteki) nie jest wyraźnie objęte standardem, więc jest całkiem możliwe, że różne implementacje będą obsługiwać przypadki poboczne inaczej.

Nie możesz po prostu uniknąć tego zamieszania, na przykład przez użycie różnych przestrzeni nazw dla dwóch instancji biblioteki statycznej (np. Przez uczynienie przestrzeni nazw, która będzie używana dla statycznego obiektu zdefiniowanego przez opcję wiersza poleceń)?


1
2017-07-16 09:20



tutaj jest przykład. Czy chcesz skompilować statyczną bibliotekę kilka razy, z inną przestrzenią nazw? - John
Tak. Jeśli umieścisz przestrzeń nazw w parametrze czasu kompilacji (np. Opcja g ++ -D ...), możesz skompilować bibliotekę statyczną używaną w dynamicznie ładowanym z przestrzenią nazw i statycznymi połączonymi w pliku wykonywalnym z innym obszarem nazw. W ten sposób dwa obiekty będą odrębne bez potrzeby zmiany użycia w kodzie źródłowym. - 6502
Myślałem także o tym rozwiązaniu, ale jest to niedopuszczalne - to tylko obejście. Co jeśli biblioteka statyczna jest bez kodu, np. Z jakiegoś projektu zewnętrznego? Ponieważ projekt działa poprawnie w systemie Windows, myślę, że powinien on być dobrym rozwiązaniem dla systemu Linux - John