Що відбувається з глобальними та статичними змінними у спільній бібліотеці, коли вона динамічно пов'язана?


127

Я намагаюся зрозуміти, що відбувається, коли модулі з глобальними та статичними змінними динамічно пов'язані з додатком. Під модулями я маю на увазі кожен проект у рішенні (я багато працюю з візуальною студією!). Ці модулі або вбудовані у * .lib або * .dll або у сам * .exe.

Я розумію, що двійковий файл програми містить глобальні та статичні дані всіх окремих одиниць перекладу (файли об’єктів) у сегменті даних (і читати лише сегмент даних, якщо const).

  • Що відбувається, коли ця програма використовує модуль A з динамічним зв'язком часу та завантаження? Я припускаю, що DLL має розділ для своїх глобальних та статичних. Чи завантажує їх операційна система? Якщо так, куди вони завантажуються?

  • А що відбувається, коли програма використовує модуль B з динамічним зв'язком під час виконання?

  • Якщо у моїй програмі є два модулі, в яких обидва використовують A і B, це копії глобальних точок A і B, створених, як зазначено нижче (якщо це різні процеси)?

  • Чи отримують DLL A і B доступ до глобальних програм?

(Будь ласка, вкажіть також свої причини)

Цитування з MSDN :

Змінні, оголошені як глобальні у файлі вихідного коду DLL, компілятором і лінкером трактуються як глобальні змінні, але кожен процес, що завантажує дану DLL, отримує власну копію глобальних змінних цього DLL. Область статичних змінних обмежена блоком, в якому оголошуються статичні змінні. Як результат, кожен процес за замовчуванням має власний екземпляр глобальних та статичних змінних DLL.

і звідси :

При динамічному пов'язуванні модулів може бути незрозуміло, чи мають різні бібліотеки власні екземпляри глобальних чи загальні глобальні.

Дякую.


3
Під модулями ви, мабуть, маєте на увазі libs . Існує пропозиція додати модулі до стандарту C ++ з більш точним визначенням того, що буде модулем, та різною семантикою, ніж звичайні бібліотеки на даний момент.
Девід Родрігес - дрибес

А, мав би це уточнити. Я розглядаю різні проекти в рішенні (я багато працюю з візуальною студією) як модулі. Ці модулі вбудовані в * .lib або * .dll.
Раджа

3
@ DavidRodríguez-dribeas Термін "модуль" - це правильний технічний термін для самостійних (повністю пов'язаних) виконуваних файлів, включаючи: виконувані програми, бібліотеки динамічних посилань (.dll) або спільні об'єкти (.so). Тут цілком доречно, а значення правильне і добре зрозуміле. Поки не існує стандартної функції з назвою "модулі", визначення її залишається традиційною, як я пояснив.
Мікаел Перссон

Відповіді:


176

Це досить відома різниця між 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 ++, це територія, що залежить від платформи, тобто це набагато менш надійний / портативний).


5
Чудова відповідь, дякую! У мене є подальший досвід: Оскільки DLL - це автономний фрагмент коду та даних, чи є в ньому розділ сегменту даних, подібний виконуваним файлам? Я намагаюся зрозуміти, куди і як завантажуються ці дані (коли), коли використовується спільна бібліотека.
Раджа

18
@Raja Так, DLL має сегмент даних. Насправді, що стосується самих файлів, виконувані файли та DLL практично однакові, єдиною реальною різницею є прапор, встановлений у виконуваному файлі, щоб сказати, що він містить "головну" функцію. Коли процес завантажує DLL, його сегмент даних кудись копіюється в адресний простір процесу, а статичний код ініціалізації (який ініціалізує нетривіальні глобальні змінні) також запускається в адресний простір процесу. Завантаження таке ж, як і для виконуваного файлу, за винятком того, що розширюється адресний простір процесу замість створеного нового.
Мікаел Перссон

4
Як щодо статичних змінних, визначених у вбудованій функції класу? наприклад, визначте "клас A {void foo () {static int st_var = 0;}}" у файлі заголовка та включіть його до модуля A та модуля B, чи A / B поділиться тим самим st_var чи кожен має свою копію?
camino

2
@camino Якщо клас експортується (тобто визначено з __attribute__((visibility("default")))), то A / B поділиться тим самим st_var. Але якщо клас визначений з __attribute__((visibility("hidden"))), то модуль A і модуль B матимуть власну копію, не спільну.
Вей Го

1
@camino __declspec (dllexport)
ruipacheco
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.