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

Объектный файл

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

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

См. также

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

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

Объектный Си — Objective C Класс языка: объектно ориентированный, мультипарадигмальный: рефлексивно ориентированный Появился в: 1986 г. Автор(ы): Типизация данных: строгая полиморфная, статическая Основные реализации: Apple gcc Испытал … Википедия

Список расширений имени файла/A — / * A B C D E F G H I J K L M N O P Q R S T U V W X Y Z Примечание: Поскольку расширение имени файла может быть любым, представленный список не является полным … Википедия

Метаобъектный компилятор — Эта статья предлагается к удалению. Пояснение причин и соответствующее обсуждение вы можете найти на странице Википедия:К удалению/25 июня 2012. Пока процесс обсуждения … Википедия

DICOM — (англ. Digital Imaging and Communications in Medicine) отраслевой стандарт создания, хранения, передачи и визуализации медицинских изображений и документов обследованных пациентов. Содержание 1 DICOM Standard 2 DICOM File 3 DICOM… … Википедия

D (язык программирования) — У этого термина существуют и другие значения, см. D. D Семантика: мультипарадигменный: императивное, объектно ориентированное, обобщённое программирование Тип исполнения: компилятор Появился в: 1999 Автор(ы) … Википедия

Компиляция — в программировании перевод программы с языка высокого уровня в машинный язык. Результатом компиляции является объектный файл с необходимыми внешними ссылками для компоновщика. См. также: Трансляторы Финансовый словарь Финам … Финансовый словарь

Дизассемблер — транслятор, преобразующий машинный код, объектный файл или библиотечные модули в текст программы на языке ассемблера. По режиму работы с пользователем делятся на Автоматические Интерактивные Примером автоматических дизассемблеров может служить… … Википедия

Блоб — О типе данных в БД см. BLOB. Блоб (от англ. binary linked object объект двоичной компоновки) объектный файл без публично доступных исходных кодов, загружаемый в ядро операционной системы. Обычно этот термин применяется только по… … Википедия

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

Источник

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

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

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

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

Исполняемый файл формируется путем связывания файлов Object.

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

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

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

– > проходит через предварительный процессор

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

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

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

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

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

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

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

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

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

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

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

Бонус:

При связывании со static library функции, на которые имеются ссылки во входных объектах, копируются в конечный исполняемый файл. Вместо dynamic libraries вместо этого создается таблица символов, которая обеспечивает динамическое связывание с библиотечными функциями/глобальными переменными. Таким образом, результатом является частично исполняемый объектный файл, так как он зависит от библиотеки. Если библиотека не существует, файл больше не может быть выполнен).

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

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

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

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

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

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

Источник

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

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

5 ответов

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

бонус:

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

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

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

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

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

Источник

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

Цель данной статьи — помочь 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 не будет опубликован. Обязательные поля помечены *