Ответ 1
Это довольно известная разница между Windows и Unix-подобными системами.
Независимо от того, что:
- Каждый процесс имеет свое собственное адресное пространство, а это означает, что между процессами никогда не используется какая-либо память (если вы не используете некоторую межпроцессную библиотеку или расширения связи).
- До сих пор применяется одно правило определения (ODR), что означает, что вы можете иметь только одно определение глобальной переменной, видимое во время ссылки (статическая или динамическая привязка).
Итак, ключевая проблема здесь - действительно видимость.
Во всех случаях глобальные переменные (или функции) static
никогда не видны извне модуля (dll/so или executable). Стандарт С++ требует, чтобы у них была внутренняя связь, что означает, что они не видны за пределами единицы перевода (которая становится объектным файлом), в которой они определены. Итак, это решает эту проблему.
Там, где это усложняется, вы имеете extern
глобальные переменные. Здесь Windows и Unix-подобные системы совершенно разные.
В случае Windows (.exe и .dll) глобальные переменные extern
не являются частью экспортированных символов. Другими словами, разные модули никоим образом не знают глобальных переменных, определенных в других модулях. Это означает, что вы получите ошибки компоновщика, если попытаетесь, например, создать исполняемый файл, который должен использовать переменную extern
, определенную в DLL, потому что это недопустимо. Вам необходимо предоставить объектный файл (или статическую библиотеку) с определением этой переменной extern и связать его статически с исполняемым и 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()
так же, как и для функций, при условии, что глобальные переменные являются частью списка экспортированных символов (по правилам предыдущих абзацев).
И, конечно, в качестве необходимой окончательной заметки: следует избегать глобальных переменных. И я считаю, что текст, который вы цитировали (о том, что "неясно" ), относится точно к различиям, специфичным для платформы, которые я только что объяснил (динамические библиотеки на самом деле не определены стандартом С++, это платформа, гораздо менее надежна/переносима).