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


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

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

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

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

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

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

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

цитирую MSDN:

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

и здесь:

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

спасибо.

1 87

1 ответ:

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

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

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

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

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

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

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

наконец, в обоих случаях, для Windows или Unix-подобных систем, вы можете сделать времени связывание динамических библиотека, т. е. с помощью LoadLibrary()/GetProcAddress()/FreeLibrary() или dlopen()/dlsym()/dlclose(). В этом случае вы должны вручную получить указатель на каждый из символов, которые вы хотите использовать, и это включает в себя глобальные переменные, которые вы хотите использовать. Для глобальных переменных можно использовать GetProcAddress() или dlsym() точно так же, как и для функций, при условии, что глобальные переменные являются частью экспортированного списка символов (по правилам предыдущих пунктов).

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