Próbuję zrozumieć, co się dzieje, gdy moduły ze zmiennymi globalnymi i statycznymi są dynamicznie połączone z aplikacją. Przez moduły rozumiem każdy projekt w rozwiązaniu (dużo pracuję z Visual Studio!). Te moduły są wbudowane w * .lib lub * .dll lub sam * .exe.
Rozumiem, że plik binarny aplikacji zawiera globalne i statyczne dane wszystkich indywidualnych jednostek tłumaczeniowych (plików obiektowych) w segmencie danych (i segment danych tylko do odczytu, jeśli jest const).
Co się dzieje, gdy ta aplikacja używa modułu A z dynamicznym łączeniem w czasie ładowania? Zakładam, że biblioteka DLL ma sekcję dotyczącą globalnych i statycznych. Czy system operacyjny je ładuje? Jeśli tak, gdzie są ładowane?
A co się dzieje, gdy aplikacja używa modułu B z dynamicznym łączeniem w czasie wykonywania?
Jeśli mam dwa moduły w mojej aplikacji, które używają A i B, czy kopie globali A i B są tworzone, jak wspomniano poniżej (jeśli są to różne procesy)?
Czy biblioteki DLL A i B uzyskują dostęp do globalnych aplikacji?
(Proszę również podać powody)
Cytowanie z MSDN :
Zmienne zadeklarowane jako globalne w pliku kodu źródłowego DLL są traktowane przez kompilator i konsolidator jako zmienne globalne, ale każdy proces ładujący daną bibliotekę DLL otrzymuje własną kopię zmiennych globalnych tej biblioteki DLL. Zakres zmiennych statycznych jest ograniczony do bloku, w którym zadeklarowane są zmienne statyczne. W rezultacie każdy proces ma domyślnie własne wystąpienie zmiennych globalnych i statycznych biblioteki DLL.
i stąd :
Podczas dynamicznego łączenia modułów może być niejasne, czy różne biblioteki mają własne instancje obiektów globalnych, czy też pliki globalne są współużytkowane.
Dzięki.
Odpowiedzi:
Jest to dość znana różnica między systemami Windows a systemami podobnymi do Uniksa.
Nieważne co:
Tak więc kluczową kwestią jest tutaj naprawdę widoczność .
We wszystkich przypadkach
static
zmienne globalne (lub funkcje) nigdy nie są widoczne spoza modułu (dll / so lub plik wykonywalny). Standard C ++ wymaga, aby miały one wewnętrzne powiązania, co oznacza, że nie są widoczne poza jednostką tłumaczeniową (która staje się plikiem obiektowym), w której są zdefiniowane. Więc to rozwiązuje ten problem.Sytuacja staje się skomplikowana, gdy masz
extern
zmienne globalne. Tutaj systemy Windows i systemy typu Unix są zupełnie inne.W przypadku systemu Windows (.exe i .dll)
extern
zmienne globalne nie są częścią eksportowanych symboli. Innymi słowy, różne moduły w żaden sposób nie są świadome zmiennych globalnych zdefiniowanych w innych modułach. Oznacza to, że wystąpią błędy konsolidatora, jeśli spróbujesz na przykład utworzyć plik wykonywalny, który powinien używaćextern
zmiennej zdefiniowanej w bibliotece DLL, ponieważ jest to niedozwolone. Będziesz musiał dostarczyć plik obiektowy (lub bibliotekę statyczną) z definicją tej zmiennej zewnętrznej i połączyć ją statycznie zarówno z plikiem wykonywalnym, jak i biblioteką DLL, w wyniku czego powstają dwie odrębne zmienne globalne (jedna należąca do pliku wykonywalnego i jedna należąca do biblioteki DLL ).Aby faktycznie wyeksportować zmienną globalną w systemie Windows, musisz użyć składni podobnej do składni eksportu / importu funkcji, tj .:
Gdy to zrobisz, zmienna globalna zostanie dodana do listy eksportowanych symboli i może być połączona jak wszystkie inne funkcje.
W przypadku środowisk uniksopodobnych (takich jak Linux) biblioteki dynamiczne, zwane „obiektami współdzielonymi” z rozszerzeniem,
.so
eksportują wszystkieextern
zmienne globalne (lub funkcje). W tym przypadku, jeśli wykonujesz łączenie w czasie ładowania z dowolnego miejsca do współdzielonego pliku obiektu, wtedy zmienne globalne są współdzielone, tj. Połączone razem jako jedna. Zasadniczo systemy uniksopodobne są zaprojektowane tak, aby praktycznie nie było różnicy między łączeniem z biblioteką statyczną lub dynamiczną. Ponownie ODR ma zastosowanie weextern
wszystkich modułach : zmienna globalna będzie współdzielona między modułami, co oznacza, że powinna mieć tylko jedną definicję dla wszystkich załadowanych modułów.Na koniec, w obu przypadkach dla Windows lub Unix-like systemów można zrobić run-time łączącego biblioteki dynamicznej, czyli przy użyciu albo
LoadLibrary()
/GetProcAddress()
/FreeLibrary()
lubdlopen()
/dlsym()
/dlclose()
. W takim przypadku musisz ręcznie uzyskać wskaźnik do każdego z symboli, których chcesz użyć, w tym zmiennych globalnych, których chcesz użyć. W przypadku zmiennych globalnych można użyć funkcjiGetProcAddress()
lubdlsym()
tak samo, jak w przypadku funkcji, pod warunkiem, że zmienne globalne są częścią listy eksportowanych symboli (zgodnie z zasadami z poprzednich akapitów).I oczywiście, jako niezbędna uwaga końcowa: należy unikać zmiennych globalnych . Uważam, że cytowany tekst (o tym, że rzeczy są „niejasne”) odnosi się dokładnie do różnic specyficznych dla platformy, które właśnie wyjaśniłem (biblioteki dynamiczne nie są tak naprawdę zdefiniowane przez standard C ++, jest to obszar specyficzny dla platformy, co oznacza, że jest znacznie mniej niezawodny / przenośny).
źródło
__attribute__((visibility("default")))
), A / B będzie współużytkować tę samą st_var. Ale jeśli klasa jest zdefiniowana za pomocą__attribute__((visibility("hidden")))
, to moduł A i moduł B będą miały własną kopię, a nie współdzielone.