Это довольно известное различие между 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 ++, это территория, зависящая от платформы, то есть намного менее надежен / портативен).