Я пытаюсь понять, что происходит, когда модули с глобальными и статическими переменными динамически связываются с приложением. Под модулями я подразумеваю каждый проект в решении (я много работаю с Visual Studio!). Эти модули либо встроены в * .lib, либо * .dll, либо в сам * .exe.
Я понимаю, что двоичный файл приложения содержит глобальные и статические данные всех отдельных единиц перевода (объектных файлов) в сегменте данных (и сегмент данных только для чтения, если const).
Что происходит, когда это приложение использует модуль A с динамической компоновкой во время загрузки? Я предполагаю, что в DLL есть раздел для глобальных переменных и статики. Загружает ли их операционная система? Если да, то куда они загружаются?
А что происходит, когда приложение использует модуль B с динамической компоновкой во время выполнения?
Если в моем приложении есть два модуля, которые оба используют A и B, создаются ли копии глобальных объектов A и B, как указано ниже (если это разные процессы)?
Получают ли библиотеки DLL A и B доступ к глобальным приложениям?
(Также укажите свои причины)
Цитата из MSDN :
Переменные, объявленные как глобальные в файле исходного кода DLL, обрабатываются компилятором и компоновщиком как глобальные переменные, но каждый процесс, загружающий данную DLL, получает свою собственную копию глобальных переменных этой DLL. Объем статических переменных ограничен блоком, в котором объявлены статические переменные. В результате каждый процесс по умолчанию имеет собственный экземпляр глобальных и статических переменных DLL.
и отсюда :
При динамическом связывании модулей может быть неясно, имеют ли разные библиотеки свои собственные экземпляры глобальных объектов или они являются общими.
Спасибо.
Ответы:
Это довольно известное различие между Windows и Unix-подобными системами.
Не важно что:
Итак, ключевым моментом здесь является наглядность .
Во всех случаях
static
глобальные переменные (или функции) никогда не видны извне модуля (dll / so или исполняемого файла). Стандарт C ++ требует, чтобы они имели внутреннюю связь, что означает, что они не видны за пределами единицы перевода (которая становится объектным файлом), в которой они определены. Итак, это решает этот вопрос.Все усложняется, когда у вас есть
extern
глобальные переменные. Здесь Windows и Unix-подобные системы совершенно разные.В случае Windows (.exe и .dll)
extern
глобальные переменные не являются частью экспортируемых символов. Другими словами, разные модули никоим образом не знают о глобальных переменных, определенных в других модулях. Это означает, что вы получите ошибки компоновщика, если попытаетесь, например, создать исполняемый файл, который должен использоватьextern
переменную, определенную в DLL, поскольку это недопустимо. Вам нужно будет предоставить объектный файл (или статическую библиотеку) с определением этой внешней переменной и связать его статически как с исполняемым файлом, так и с DLL, в результате чего получатся две отдельные глобальные переменные (одна принадлежит исполняемому файлу, а другая - DLL. ).Чтобы на самом деле экспортировать глобальную переменную в Windows, вы должны использовать синтаксис, аналогичный синтаксису экспорта / импорта функции, то есть:
При этом глобальная переменная добавляется к списку экспортируемых символов и может быть связана, как и все другие функции.
В случае Unix-подобных сред (например, Linux) динамические библиотеки, называемые «разделяемыми объектами» с расширением,
.so
экспортируют всеextern
глобальные переменные (или функции). В этом случае, если вы выполняете привязку во время загрузки из любого места к общему объектному файлу, то глобальные переменные являются общими, т. Е. Связываются вместе как одна. По сути, Unix-подобные системы спроектированы так, чтобы практически не было разницы между связыванием со статической или динамической библиотекой. Опять же, ODR применяется повсеместно:extern
глобальная переменная будет совместно использоваться всеми модулями, а это означает, что она должна иметь только одно определение для всех загруженных модулей.Наконец, в обоих случаях для Windows или Unix-подобных систем вы можете выполнить компоновку динамической библиотеки во время выполнения, то есть используя либо
LoadLibrary()
/GetProcAddress()
/,FreeLibrary()
либоdlopen()
/dlsym()
/dlclose()
. В этом случае вам нужно вручную получить указатель на каждый из символов, которые вы хотите использовать, включая глобальные переменные, которые вы хотите использовать. Для глобальных переменных вы можете использоватьGetProcAddress()
илиdlsym()
точно так же, как и для функций, при условии, что глобальные переменные являются частью экспортируемого списка символов (по правилам предыдущих абзацев).И, конечно же, в качестве необходимого заключительного замечания: следует избегать глобальных переменных . И я считаю, что процитированный вами текст (о том, что «неясно») относится именно к различиям, зависящим от платформы, которые я только что объяснил (динамические библиотеки на самом деле не определены стандартом C ++, это территория, зависящая от платформы, то есть намного менее надежен / портативен).
источник
__attribute__((visibility("default")))
), то A / B будет использовать одну и ту же st_var. Но если класс определен с__attribute__((visibility("hidden")))
, то модуль A и модуль B будут иметь свою собственную копию, а не общую.