Подтвердить что ты не робот

Что происходит с глобальными и статическими переменными в общей библиотеке, когда она динамически связана?

Я пытаюсь понять, что происходит, когда модули с глобальными и статическими переменными динамически связаны с приложением. По модулю я имею в виду каждый проект в решении (я много работаю с визуальной студией!). Эти модули либо встроены в *.lib, либо *.dll, либо сами *.exe.

Я понимаю, что двоичный файл приложения содержит глобальные и статические данные всех отдельных единиц перевода (объектных файлов) в сегменте данных (и только сегмент чтения только если const).

  • Что происходит, когда это приложение использует модуль A с динамической связью времени загрузки? Я предполагаю, что в DLL есть раздел для его глобальных и статических данных. Загружает ли операционная система? Если да, то где они загружаются в?

  • И что происходит, когда приложение использует модуль B с динамической привязкой во время выполнения?

  • Если у меня есть два модуля в моем приложении, которые используют A и B, являются копиями A и B-глобалов, созданных, как указано ниже (если это разные процессы)?

  • У DLLs A и B доступ к глобальным приложениям приложений?

(Просьба также указать ваши причины)

Цитата из MSDN:

Переменные, объявленные как глобальные в файле исходного кода DLL, рассматриваются как глобальные переменные компилятором и компоновщиком, но каждый процесс, который загружает данную DLL, получает свою собственную копию этих глобальных переменных DLL. Область статических переменных ограничена блоком, в котором объявлены статические переменные. В результате каждый процесс имеет свой собственный экземпляр глобальных и статических переменных DLL по умолчанию.

и здесь:

При динамическом связывании модулей может быть непонятно, есть ли в разных библиотеках свои собственные экземпляры глобальных переменных или общие глобальные таблицы.

Спасибо.

4b9b3361

Ответ 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() так же, как и для функций, при условии, что глобальные переменные являются частью списка экспортированных символов (по правилам предыдущих абзацев).

И, конечно, в качестве необходимой окончательной заметки: следует избегать глобальных переменных. И я считаю, что текст, который вы цитировали (о том, что "неясно" ), относится точно к различиям, специфичным для платформы, которые я только что объяснил (динамические библиотеки на самом деле не определены стандартом С++, это платформа, гораздо менее надежна/переносима).