Це досить відома різниця між Windows і Unix-подібними системами.
Немає значення що:
- Кожен процес має власний адресний простір, що означає, що між процесами ніколи не ділиться жодна пам'ять (якщо ви не використовуєте якусь міжпроцесорну бібліотеку зв'язку або розширення).
- One Definition Rule (УСО) все ще застосовується, а це означає , що ви можете мати тільки одне визначення глобальної змінної видимої на час компонування (статичну або динамічне зв'язування).
Отже, ключовим питанням тут є насправді наочність .
У всіх випадках static
глобальні змінні (або функції) ніколи не видно зовні модуля (dll / so або виконуваного). Стандарт C ++ вимагає, щоб вони мали внутрішню зв'язок, тобто вони не були видимі поза блоком перекладу (який стає об’єктним файлом), в якому вони визначені. Отже, це вирішує це питання.
Де це ускладнюється, коли у вас є extern
глобальні змінні. Тут Windows і Unix-подібні системи абсолютно різні.
У випадку Windows (.exe та .dll) extern
глобальні змінні не є частиною експортованих символів. Іншими словами, різні модулі жодним чином не усвідомлюють глобальних змінних, визначених в інших модулях. Це означає, що ви отримаєте помилки в посиланнях, якщо спробуєте, наприклад, створити виконуваний файл, який повинен використовувати extern
змінну, визначену в DLL, оскільки це заборонено. Вам потрібно буде надати об’єктному файлу (або статичній бібліотеці) визначення цієї зовнішньої змінної і статично пов’язати її як з виконуваним файлом, так і з DLL, в результаті чого з'являються дві чіткі глобальні змінні (одна належить виконуваному файлу, а одна належить DLL ).
Щоб фактично експортувати глобальну змінну в Windows, потрібно використовувати синтаксис, аналогічний синтаксису функції експорту / імпорту, тобто:
#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;
Коли ви це зробите, глобальна змінна додається до списку експортованих символів і може бути пов'язана, як і всі інші функції.
У випадку з Unix-подібними середовищами (як-от Linux) динамічні бібліотеки, які називаються "спільними об'єктами" з розширенням .so
експортують усі extern
глобальні змінні (або функції). У цьому випадку, якщо ви здійснюєте посилання на час завантаження з будь-якого місця до спільного файлу об'єкта, то глобальні змінні розділяються, тобто пов'язані між собою як одна. В основному Unix-подібні системи розроблені так, щоб практично не було різниці між зв'язком зі статичною або динамічною бібліотекою. Знову-таки, ODR застосовується в усьому платі: extern
глобальна змінна буде розділена між модулями, тобто, вона повинна мати лише одне визначення для всіх завантажених модулів.
Нарешті, в обох випадках, для Windows , або Unix-подібних системах, ви можете зробити під час виконання зв'язування бібліотеки динамічної, тобто, використовуючи або LoadLibrary()
/ GetProcAddress()
/ FreeLibrary()
або dlopen()
/ dlsym()
/ dlclose()
. У цьому випадку вам потрібно вручну отримати вказівник на кожен із символів, який ви хочете використовувати, і що включає глобальні змінні, які ви хочете використовувати. Для глобальних змінних ви можете використовувати GetProcAddress()
або dlsym()
просто те саме, що ви робите для функцій, за умови, що глобальні змінні є частиною експортованого списку символів (за правилами попередніх пунктів).
І звичайно, як необхідна остаточна примітка: глобальних змінних слід уникати . І я вважаю, що цитований вами текст (про "незрозумілі речі") стосується саме певних платформних відмінностей, які я щойно пояснив (динамічні бібліотеки насправді не визначені стандартом C ++, це територія, що залежить від платформи, тобто це набагато менш надійний / портативний).