что такое объектные файлы

Что такое объектный файл в C?

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

5 ответов

объектный файл является реальным выходом из фазы компиляции. Это в основном машинный код, но есть информация, которая позволяет компоновщику видеть, какие символы в нем, а также символы, необходимые для работы. (Для справки, «символы» в основном имена глобальных объектов, функций и т. д.)

объектный файл-это сам скомпилированный файл. Между ними нет никакой разницы.

исполняемый файл формируется путем связывания объектные файлы.

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

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

вот типичный поток высокого уровня для этого процесса для кода на языке высокого уровня, таком как C

—> проходит через pre-processor

—> чтобы дать оптимизированный код, все еще в C

—> проходит через компилятор

—> для того чтобы дать агрегат код

—> проходит через ассемблер

—> дать код на машинном языке, который хранится в объектных файлах

—> проходит через Linker

—> чтобы получить исполняемый файл.

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

есть 3 вида объектных файлов.

перемещаемые объектные файлы

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

общие объектные файлы

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

исполняемый объект файлы

они содержат машинный код, который может быть непосредственно загружен в память (загрузчиком, e.G execve) и впоследствии выполнен.

бонус:

1: на самом деле это collect2, который является оболочкой над ld.

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

Это может быть либо полностью завершенный исполняемый файл, либо библиотека, либо промежуточные файлы.

объектные файлы обычно содержат собственный код, информацию компоновщика, отладочные символы и т. д.

объектный код-это коды, которые зависят от функций, символов, текста для запуска машины. Просто как старый телекс машин которые требовали teletyping посылают сигналы другим телекс машина. Таким же образом процессор требует двоичного кода для запуска машины. Объектный файл похож на двоичный код, но не связан. Связывание создает дополнительные файлы, так что пользователь не должен иметь компилятор языка Си. Пользователь может напрямую открыть exe-файл, как только объектный файл связан с некоторым компилятором, таким как c язык, или vb etc.

Источник

Объектный модуль

Объе́ктный мо́дуль (также — объектный файл, англ. object file ) — файл с промежуточным представлением отдельного модуля программы, полученный в результате обработки исходного кода компилятором. Объектный файл содержит в себе особым образом подготовленный код (часто называемый бинарным), который может быть объединён с другими объектными файлами при помощи редактора связей (компоновщика) для получения готового исполнимого модуля, либо библиотеки.

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

См. также

Полезное

Смотреть что такое «Объектный модуль» в других словарях:

Объектный модуль — согласно ГОСТ 19781 90 программный модуль, получаемый в результате трансляции исходного модуля. Объектный модуль программа на машинном языке с неразрешенными внешними ссылками. См. также: Программные модули Финансовый словарь Финам … Финансовый словарь

объектный модуль — Программный модуль, получаемый в результате компиляции исходного модуля. Примечание Объектный модуль обычно полностью готов к редактированию связей. [ГОСТ 19781 90] Тематики обеспеч. систем обраб. информ. программное EN object module … Справочник технического переводчика

Объектный модуль — 17. Объектный модуль Object module Программный модуль, получаемый в результате компиляции исходного модуля. Примечание. Объектный модуль обычно полностью готов к редактированию связей Источник: ГОСТ 19781 90: Обеспечение систем обработки… … Словарь-справочник терминов нормативно-технической документации

Модуль — (от лат. modulus «маленькая мера»): В Викисловаре есть статья «модуль» Мо … Википедия

Модуль (значения) — Модуль (от лат. modulus «маленькая мера») составная часть, отделимая или хотя бы мысленно выделяемая из общего. Модульной обычно называют вещь, состоящую из чётко выраженных частей, которые нередко можно убирать или добавлять, не разрушая вещь… … Википедия

Модуль объектный — 17 76 Источник: ГОСТ 19781 90: Обеспечение систем обработки информации программное. Термины и определения … Словарь-справочник терминов нормативно-технической документации

конечный модуль — выходной модуль рабочий модуль объектный модуль — [Л.Г.Суменко. Англо русский словарь по информационным технологиям. М.: ГП ЦНИИС, 2003.] Тематики информационные технологии в целом Синонимы выходной модульрабочий модульобъектный модуль EN… … Справочник технического переводчика

Загружаемый модуль ядра — (loadable kernel module, LKM) в информатике объектный файл, содержащий код, расширяющий возможности ядра операционной системы. Модули используются, чтобы добавить поддержку нового оборудования или файловых систем или для добавления… … Википедия

ГОСТ 19781-90: Обеспечение систем обработки информации программное. Термины и определения — Терминология ГОСТ 19781 90: Обеспечение систем обработки информации программное. Термины и определения оригинал документа: 9. Абсолютная программа Non relocatable program Программа на машинном языке, выполнение которой зависит от ее… … Словарь-справочник терминов нормативно-технической документации

Источник

Руководство новичка по эксплуатации компоновщика

Цель данной статьи — помочь C и C++ программистам понять сущность того, чем занимается компоновщик. За последние несколько лет я объяснил это большому количеству коллег и наконец решил, что настало время перенести этот материал на бумагу, чтоб он стал более доступным (и чтоб мне не пришлось объяснять его снова). [Обновление в марте 2009: добавлена дополнительная информация об особенностях компоновки в Windows, а также более подробно расписано правило одного определения (one-definition rule).

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

Если Ваша реакция — ‘наверняка забыл extern «C»’, то Вы скорее всего знаете всё, что приведено в этой статье.

Содержание

Определения: что находится в C файле?

Эта глава — краткое напоминание о различных составляющих C файла. Если всё в листинге, приведённом ниже, имеет для Вас смысл, то скорее всего Вы можете пропустить эту главу и сразу перейти к следующей.

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

Подытожим:

КодДанные
ГлобальныеЛокальныеДинамические
Инициа-
лизиро-
ванные
Неинициа-
лизиро-
ванные
Инициа-
лизиро-
ванные
Неинициа-
лизиро-
ванные
Объяв-
ление
int fn(int x);extern int x;extern int x;N/AN/AN/A
Опреде-
ление
int fn(int x)

int x = 1;
(область действия
— файл)
int x;
(область действия — файл)
int x = 1;
(область действия — функция)
int x;
(область действия — функция)
int* p = malloc(sizeof(int));

Вероятно более лёгкий путь усвоить — это просто посмотреть на пример программы.

Что делает C компилятор

Где бы код ни ссылался на переменную или функцию, компилятор допускает это, только если он видел раньше объявление этой переменной или функции. Объявление — это обещание, что определение существует где-то в другом месте программы.

Работа компоновщика проверить эти обещания. Однако, что компилятор делает со всеми этими обещаниями, когда он генерирует объектный файл?

По существу компилятор оставляет пустые места. Пустое место (ссылка) имеет имя, но значение соответствующее этому имени пока не известно.

Учитывая это, мы можем изобразить объектный файл, соответствующей программе, приведённой выше, следующим образом:
что такое объектные файлы. Смотреть фото что такое объектные файлы. Смотреть картинку что такое объектные файлы. Картинка про что такое объектные файлы. Фото что такое объектные файлы

Анализирование объектного файла

Давайте посмотрим, что выдаёт nm для объектного файла, полученного из нашего примера выше:

Что делает компоновщик: часть 1

Ранее мы обмолвились, что объявление функции или переменной — это обещание компилятору, что где-то в другом месте программы есть определение этой функции или переменной, и что работа компоновщика заключается в осуществлении этого обещания. Глядя на диаграмму объектного файла, мы можем описать этот процесс, как «заполнение пустых мест».

Проиллюстрируем это на примере, рассматривая ещё один C файл в дополнение к тому, что был приведён выше.

что такое объектные файлы. Смотреть фото что такое объектные файлы. Смотреть картинку что такое объектные файлы. Картинка про что такое объектные файлы. Фото что такое объектные файлы

Исходя из обоих диаграмм, мы можем видеть, что все точки могут быть соединены (если нет, то компоновщик выдал бы сообщение об ошибке). Каждая вещь имеет своё место, и каждое место имеет свою вещь. Также компоновщик может заполнить все пустые места как показано здесь (на системах UNIX процесс компоновки обычно вызывается командой ld ).

что такое объектные файлы. Смотреть фото что такое объектные файлы. Смотреть картинку что такое объектные файлы. Картинка про что такое объектные файлы. Фото что такое объектные файлы

Также как и для объектных файлов, мы можем использовать nm для исследования конечного исполняемого файла.

Он содержит символы обоих объектных файлов и все неопределённые ссылки исчезли. Символы переупорядочены так, что похожие типы находятся вместе. А также существует немного дополнений, чтобы помочь ОС иметь дело с такой штукой, как исполняемый файл.

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

Повторяющиеся символы

В предыдущей главе было упомянуто, что компоновщик выдаёт сообщение об ошибке, если не может найти определение для символа, на который найдена ссылка. А что случится, если найдено два определения для символа во время компоновки?

В C++ решение прямолинейное. Язык имеет ограничение, известное как правило одного определения, которое гласит, что должно быть только одно определение для каждого символа, встречающегося во время компоновки, ни больше, ни меньше. (Соответствующей главой стандарта C++ является 3.2, которая также упоминает некоторые исключения, которые мы рассмотрим несколько позже.)

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

Однако, компоновщики должны уметь обходится также и с другими языками кроме C и C++, для которых правило одного определения не обязательно соблюдается. Например, для Fortran’а является нормальным иметь копию каждой глобальной переменной в каждом файле, который на неё ссылается. Компоновщику необходимо тогда убрать дубликаты, выбрав одну копию (самого большого представителя, если они отличаются в размере) и выбросить все остальные. Эта модель иногда называется «общей моделью» компоновки из-за ключевого слова COMMON (общий) языка Fortran.

Что делает операционная система

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

Запуск программы разумеется влечёт за собой выполнение машинного кода, т.е. ОС очевидно должна перенести машинный код исполняемого файла с жёстокого диска в операционную память, откуда CPU сможет его забрать. Эти порции называются сегментом кода (code segment или text segment).

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

Для неинициализированных переменных ОС может предположить, что они все имеют 0 в качестве начального значения, т.е. нет надобности копировать какие-либо значения. Кусок памяти, который инициализируется нулями, известен как bss сегмент.

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

что такое объектные файлы. Смотреть фото что такое объектные файлы. Смотреть картинку что такое объектные файлы. Картинка про что такое объектные файлы. Фото что такое объектные файлы

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

что такое объектные файлы. Смотреть фото что такое объектные файлы. Смотреть картинку что такое объектные файлы. Картинка про что такое объектные файлы. Фото что такое объектные файлы

Что делает компоновщик; часть 2

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

Главное наблюдение, которое затрагивает функции компоновщика следующее: если ряд различных программ делают примерно одни и те же вещи (вывод на экран, чтение файлов с жёсткого диска и т.д.), тогда очевидно имеет смысл обособить этот код в определённом месте и дать другим программам его использовать.

Одним из возможных решений было бы использование одних и тех же объектных файлов, однако было бы гораздо удобнее держать всю коллекцию… объектных файлов в одном легко доступном месте: библиотеке.

Техническое отступление: Эта глава полностью опускает важное свойство компоновщика: переадресация (relocation). Разные программы имеют различные размеры, т.е. если разделяемая библиотека отображается в адресное пространство различных программ, она будет иметь различные адреса. Это в свою очередь означает, что все функции и переменные в библиотеке будут на различных местах. Теперь, если все обращения к адресам относительные («значение +1020 байта отсюда») нежели абсолютные («значение в 0x102218BF»), то это не проблема, однако так бывает не всегда. В таких случаях всем абсолютным адресам необходимо прибавить подходящий офсет — это и есть relocation. Я не собираюсь возвращается к этой теме снова, однако добавлю, что так как это практически всегда скрыто от C/C++ программиста — очень редко проблемы компоновки вызваны трудностями переадресации.

Статические библиотеки

Самое простое воплощение библиотеки — это статическая библиотека. В предыдущей главе было упомянуто, что можно разделять (share), код просто повторно используя объектные файлы; это и есть суть статичных библиотек.

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

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

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

(Между прочим этот пример имеет циклическую зависимость между библиотеками libx.a и liby.a ; обычно это плохо особенно под Windows)

Динамические разделяемые библиотеки

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

Существует другое большое отличие между тем, как динамические библиотеки работают по сравнению со статическими и это проявляется в гранулярности компоновки. Если конкретный символ берётся из конкретной динамической библиотеки (скажем printf из libc.so ), то всё содержимое библиотеки помещается в адресное пространство программы. Это основное отличие от статических библиотек, где добавляются только конкретные объекты, относящиеся к неопределённому символу.

Windows DLL

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

Экспортируемые символы

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

Как только к этой мешанине подключается C++, первая из этих опций становится самой простой, так как в этом случае компилятор берёт на себя обязательства позаботиться о декорировании имён

.LIB и другие относящиеся к библиотеке файлы

Импортируемые символы

Вместе с требованием к DLL явно объявлять экспортируемые символы, Windows также разрешает бинарникам, которые используют код библиотеки, явно объявлять символы, подлежащие импортированию. Это не является обязательным, но даёт некоторую оптимизацию по скорости, вызванную историческими свойствами 16-ти битных окон.

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

Стандартный выход из этой ситуации — это использование макросов препроцессора.

Файл с исходниками в DLL, который определяет функцию и переменную гарантирует, что переменная препроцессора EXPORTING_XYZ_DLL_SYMS определена (по средством #define ) до включения соответствующего заголовочного файла и таким образом экспортирует символ. Любой другой код, который включает этот заголовочный файл не определяет этот символ и таким образом импортирует его.

Циклические зависимости

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

Для большинства систем — это не проблема. Выполняемые файлы зависят от высокоуровевых библиотек, высокоуровневые библиотеки зависят от библиотек низкого уровня, и всё компонуется в обратном порядке — сначала библиотеки низкого уровня, потом высокого, а затем и выполняемый файл, который зависит от всех остальных

C++ для дополнения картины

C++ предлагает ряд дополнительных возможностей сверх того, что доступно в C, и часть этих возможностей влияет на работу компоновщика. Так было не всегда — первые реализации C++ появились в качестве внешнего интерфейса к компилятору C, поэтому о совместимости работы компоновщика не было нужды. Однако со временем были добавлены более продвинутые особенности языка, так что компоновщик уже должен был быть изменён, чтобы их поддерживать.

Перегрузка функций и декорирование имён

Решение к этой проблеме названо декорированием имён (name mangling), потому что вся информация о сигнатуре функции переводится (to mangle = искажать, деформировать, прим.пер.) в текстовую форму, которая становится собственно именем символа с точки зрения компоновщика. Различные сигнатуры переводятся в различные имена. Таким образом проблема уникальности имён решена.

Я не собираюсь вдаваться в детали используемых схем декорирования (которые к тому же отличаются от платформы к платформе), но беглый взгляд на объектный файл, соответствующий коду выше, даст идею, как всё это понимать (запомните, nm — Ваш друг!):

Область, где схемы декорирования чаще всего заставляют ошибиться, находится в месте переплетения C и C++. Все символы, произведённые C++ компилятором, декорированы; все символы, произведённые C компилятором, выглядят так же, как и в исходном коде. Чтобы обойти это, язык C++ разрешает поместить extern «C» вокруг объявления и определения функций. По сути этим мы сообщаем C++ компилятору, что определённое имя не должно быть декорировано — либо потому что это определение C++ функции, которая будет вызываться кодом C, либо потом что это определение C функции, которая будет вызываться кодом C++.

Возвращаясь к примеру в самом начале статьи, можно легко заметить, что существует достаточно большая вероятность, что кто-то забыл использовать extern «C» при компоновке C и C++ объектов.

Кстати заметьте, что объявление extern «C» игнорируется для функций-членов классов (§7.5.4 стандарта С++)

Инициализация статических объектов

Следующее выходящее за рамки С свойство C++, которое затрагивает работу компоновщика, — это существование конструкторов объектов. Конструктор — это кусок кода, который задаёт начальное состояние объекта. По сути его работа концептуально эквивалентна инициализации значения переменной, однако с той важной разницей, что речь идёт о произвольных фрагментах кода.

Вспомним из первой главы, что глобальные переменные могут начать своё существование уже с определённым значением. В C конструкция начального значения такой глобальной переменной — дело простое: определённое значение просто копируется из сегмента данных выполняемого файла в соответствующее место в памяти программы, которая вот-вот-начнёт-выполняться.

В C++ процесс инициализации может быть гораздо сложнее, чем просто копирование фиксированных значений; весь код в различных конструкторах по всей иерархии классов должен быть выполнен, прежде чем сама программа фактически начнёт выполняться.

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

Обратим внимание, что порядок, в котором конструкторы глобальных объектов вызываются не определён — он полностью находится во власти того, что именно компоновщик намерен делать. (См. «Эффективный C++» Скотта Майерса для дальнейших деталей — заметка 47 во второй редакции, заметка 4 в третьей редакции)

Для этого кода (недекорированный) вывод nm выглядит так:

Как обычно, мы можем увидеть здесь кучу разных вещей, но одна из них наиболее интересна для нас это записи с классом W (что означает «слабый» символ («weak» symbol)) а также записи именем секции типа «.gnu.linkonce.t.stuff«. Это маркеры для конструкторов глобальных объектов и мы видим, что соответствующее поле «Name» показывает то, что мы собственно и могли там ожидать — каждый из двух конструкторов задействованы.

Шаблоны

C++ вводит понятия шаблона (templates), который позволяет использовать код, приведённый ниже, сразу для всех случаев. Мы можем создать заголовочный файл max_template.h с только одной копией кода функции max :

и включим этот файл в исходный файл, чтобы испробовать шаблонную функцию:

Каждая из этих различных инстанций порождает различный машинный код. Таким образом на то время, когда программа будет окончательна скомпонована, компилятор и компоновщик должны гарантировать, что код каждого используемого экземпляра шаблона включён в программу (и ни один неиспользуемый экземпляр шаблона не включён, чтобы не раздуть размер программы).

Как же это делается? Обычно есть два пути действия: либо прореживание повторяющихся инстанций либо откладывание инстанциирования до стадии компоновки (я обычно называю эти подходы как разумный путь и путь компании Sun).

Способ прореживания повторяющихся инстанций подразумевает, что каждый объектный файл содержит код всех повстречавшихся шаблонов. Например, для приведённого выше файла, содержимое объектного файла выглядит так:

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

Другой подход (который используется в Solaris C++) — это не включать шаблонные определения в объектные файлы вообще, а пометить их как неопределённые символы. Когда дело доходит до стадии компоновки, то компоновщик может собрать все неопределённые символы, которые собственно относятся к шаблонным инстанциям, и потом сгенерировать машинный код для каждой из них.

Это определённо редуцирует размер каждого объектного файла, однако минус этого подхода проявляется в том, что компоновщик должен отслеживать где исходной код находится и должен уметь запускать C++ компилятор во время компоновки (что может замедлить весь процесс)

Динамически загружаемые библиотеки

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

Это осуществляется парой системных вызовов dlopen и dlsym (примерные эквиваленты в Windows соответственно называются LoadLibrary и GetProcAddress ). Первый берёт имя разделяемой библиотеки и догружает её в адресное пространство запущенного процесса. Конечно, эта библиотека может также иметь неразрешённые символы, поэтому вызов dlopen может повлечь за собой подгрузку других разделяемых библиотек.

dlopen предлагает на выбор либо ликвидировать все неразрешённости сразу, как только библиотека загружена, ( RTLD_NOW ) либо разрешать символы по мере необходимости ( RTLD_LAZY ). Первый способ означает, что вызов dlopen может занять достаточно времени, однако второй способ закладывает определённый риск, что во время выполнения программы будет обнаружена неопределённая ссылка, которая не может быть разрешена — в этот момент программа будет завершена.

Взаимодействие с C++

Процесс динамической загрузки достаточно прямолинеен, но как он взаимодействует с различными особенностями C++, которые воздействуют на всё поведение компоновщика?

Так как процесс декорирования может меняться от платформы к платформе и от компилятора к компилятору, это означает, что практически невозможно динамически найти C++ символ универсальным методом. Даже если Вы работаете только с одним компилятором и углубляетесь в его внутренний мир, существуют и другие проблемы — кроме простых C-подобных функций, есть куча других вещей (таблицы виртуальных методов и тому подобное), о которых тоже надо заботиться.

Подводя итог изложенному выше, отметим следующее: обычно лучше иметь одну заключённую в extern «C» точку вхождения, которая может быть найдена dlsym ‘ом. Эта точка вхождения может быть фабричным методом, который возвращает указатели на все инстанции C++ класса, разрешая доступ ко всем прелестям C++.

И в заключении добавим, что динамическая загрузка справляется отлично с «прореживанием повторяющихся инстанций», если речь идёт об инстанциировании шаблонов; и всё выглядит неоднозначно с «откладыванием инстанциирования», так как «стадия компоновки» наступает после того, как программа уже запущена (и вполне вероятно на другой машине, которая не хранит исходники). Обращайтесь к документации компилятора и компоновщика, чтобы найти выход из такой ситуации.

Дополнительно

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

Many thanks to Mike Capp and Ed Wilson for useful suggestions about this page.

Copyright © 2004-2005,2009-2010 David Drysdale

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is available here.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *