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

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

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

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

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

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

  • получают ли DLL A и B доступ к глобалам приложений?

(пожалуйста, укажите причины)

цитирую MSDN:

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

и здесь:

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

спасибо.

1 ответов


это довольно известная разница между Windows и Unix-подобных систем.

несмотря ни на что:

  • каждого процесс имеет собственное адресное пространство, что означает, что между процессами никогда не используется общая память (если вы не используете библиотеку межпроцессной связи или расширения).
  • на Одно Правило Определения (ODR) по-прежнему применяется, что означает, что вы можете иметь только одно определение глобального переменная видимая на соединени-времени (статическом или динамическом соединять).

Итак, ключевой вопрос здесь действительно видимость.

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

где это становится сложнее, когда у вас есть extern глобальные переменные. Здесь Windows и Unix-подобные системы совершенно разные.

в случае Windows (.exe и .dll файлы), то extern глобальные переменные не являются частью экспортируемых символов. Другими словами, различные модули никоим образом не знают о глобальных переменных, определенных в других модулях. Это означает, что вы получите ошибки компоновщика, если попытаетесь, например, создать исполняемый файл, который должен используйте extern переменная, определенная в DLL, потому что это не разрешено. Вам нужно будет предоставить объектный файл( или статическую библиотеку) с определением этой внешней переменной и связать его статически с и исполняемый файл и DLL, что приводит к двум различным глобальным переменным (одна принадлежит исполняемому файлу и одна принадлежит DLL).

чтобы экспортировать глобальную переменную в Windows, вы должны использовать синтаксис, аналогичный функции export/import синтаксис, т. е.:

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