Jest to dość znana różnica między systemami Windows a systemami podobnymi do Uniksa.
Nieważne co:
- Każdy proces ma własną przestrzeń adresową, co oznacza, że żadna pamięć nie jest współużytkowana między procesami (chyba że używasz jakiejś biblioteki lub rozszerzeń komunikacji między procesami).
- Zasada jedna definicja (ODR) nadal obowiązuje, co oznacza, że można mieć tylko jedną definicję zmiennej globalnej widocznym na link-czasu (statyczne lub dynamiczne łączenie).
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 .:
#ifdef COMPILING_THE_DLL
#define MY_DLL_EXPORT extern "C" __declspec(dllexport)
#else
#define MY_DLL_EXPORT extern "C" __declspec(dllimport)
#endif
MY_DLL_EXPORT int my_global;
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ą wszystkie extern
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 we extern
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()
lub dlopen()
/ 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ć funkcji GetProcAddress()
lub dlsym()
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).