Экспорт функций классов C++
Не могу понять как C++ передаёт информацию о функциях, принадлежащих классу, для нашей программы. К примеру, мы имеем заголовок, в котором в том числе указано:
class Object {
public:
void Func();
}
Как он определяет адрес этой функции в DLL, чтобы мы могли её вызывать?
Ответы (1 шт):
Если кратко, при компиляции компилятор превращает имена функций/функций-членов и т.д. в имена символов по специальному алгоритму, говоря цеховым жаргоном, мэнглит (менлит, манглит, декорирование имён¹, name decoration, см. name mangling). Этот алгоритм не стандартизован, у каждого компилятора он свой. Так, например, Object::Func() становится ?Func@Object@@QEAAXXZ (msvc) или _ZN6Object4FuncEv (gcc). При этом между заменглиным именем и обычным есть взаимооднозначное соответствие: для каждой функции будет уникальное имя, а каждому корректному имени соответствует уникальная функция. Функции языка Си (в отличие от C++) обычно не подвергаются этому процессу или изменяются тривиальным образом (например, добавляется _ перед именем)². Одна из основных мотиваций применения такого подхода — обеспечить уникальное имя для перегруженных и шаблонных функций.
Так вот, при компиляции и линковке в динамические библиотеки (и исполняемые файлы тоже, в большинстве систем у них одинаковый формат) в бинарный файл записывается специальная секция таблица символов. В ней записывается соответствие заменглиного имени и смещение каждой функции внутри библиотеки.
При вызове функции, находящейся в динамической библиотеке, этот вызов превращается компилятором/линкером в нечто подобное:
1010: call <ADDR>
...
ADDR: jmp GOT[OBJECT_FOO_OFFSET]
Где GOT (global offset table в терминах linux, глобальная таблица смещений) — это специальный объект, в который загружаются адреса всех динамически линкуемых символов, по сути это просто массив адресов всех функций, импортируемых из внешних библиотек.
GOT заполняется при запуске программы: по сути, при загрузке динамически слинкованного исполняемого файла ОС (или специальная программа, динамический линковщик) ищет имена всех импортируемых символов в динамических библиотеках и собственно заполняет эту таблицу, обеспечивая связанность. В результате чего код выше корректно работает.
У этого процесса есть и другие варианты и детали реализации, некоторые упрощают его, некоторые обеспечивают дополнительную гибкость и безопасность, но общая схема остаётся примерно такой.
¹ Спасибо @EOF за указание термина.
² Спасибо @Croessmah за уточнение.