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

от admin

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

Видео: Запись ОБЪЕКТА КЛАССА в файл с++. Чтение объекта из файла c++ Для начинающих. Урок #117

Содержание

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

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

Ключевые области покрыты

1. Что такое объектный файл
— определение, функциональность
2. Что такое исполняемый файл
— определение, функциональность
3. Какова связь между объектным файлом и исполняемым файлом
— Схема ассоциации
4. В чем разница между объектным файлом и исполняемым файлом
— Сравнение основных различий

Основные условия

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

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

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

Рисунок 1: Исходный код

Объектный файл — это другое имя для объектного кода. Объектный файл имеет расширение .obj в среде Windows. Кроме того, объектный файл имеет. o расширение файла в среде Linux. Однако ЦП не может напрямую выполнить объектный файл.

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

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

Рисунок 2: Создание исполняемого файла

Программист не понимает инструкции в исполняемом файле, но процессор может прочитать и понять эти инструкции. Следовательно, ЦПУ непосредственно выполняет исполняемый файл для выполнения определенных задач в программе.

Связь между объектным файлом и исполняемым файлом

  • Объектные файлы объединяются, чтобы создать исполняемый файл.

Разница между объектным файлом и исполняемым файлом

Определение

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

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

функциональность

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

Прямое выполнение процессором

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

Заключение

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

Ссылка:

1. «Линкер». ВикипедияФонд Викимедиа, 6 октября 2018 года,

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

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

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

См. также

Wikimedia Foundation . 2010 .

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

Объектный модуль — (также объектный файл, англ. 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)  в информатике  объектный файл, содержащий код, расширяющий возможности ядра операционной системы. Модули используются, чтобы добавить поддержку нового оборудования или файловых систем или для добавления… … Википедия

C2017/Сборка программ на C

В общем случае исполняемый файл (англ. executable file) — файл, содержащий программу в виде инструкций, которые могут быть исполнены компьютером. Исполняемым файлам противопоставляются файлы с данными (data file) — файлы, которые читаются и парсятся определённой программой, а сами по себе не могут быть исполнены.

Инструкции (код) — это:

  • либо машинные инструкции для выполнения на физическом процессоре;
  • либо исходный код (сценарий, скрипт, псевдокод), записанный на одном из интерпретируемых языков программирования (пример: bash-скрипты, Python-программы, bat-файлы);
  • либо байт-код виртуальной машины (пример: class-файлы JVM, pyc-файлы для Python).

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

Формат и отличительные особенности исполняемых файлов зависят от операционной системы. Однако концепция является общей.

Формат в UNIX

В UNIX-подобных системах (Linux, FreeBSD, . ) исполняемые файлы отличаются по специальному атрибуту execute в файловой системе, расширение файла значение не имеет (обычно не имеют расширения).

В современных UNIX-подобных системах используется формат ELF (Executable and Linkable Format).

Слово ELF в английском также имеет значение «эльф». В продолжение темы средневекового фэнтези, широко используемый формат представления отладочной информации был назван DWARF (англ. «гоблин», «гном»). Изначально проектировался вместе с ELF, название дано в том же стиле, и позже придумана расшифровка Debugging With Attributed Record Formats.

До ELF, в семидесятые годы использовался формат a.out (assembler output). Отсюда пошла традиция, что при компиляции в GCC по умолчанию выходной файл называется a.out, хотя имеет формат ELF.

Формат в Mac OS

В операционных системах от Apple (iOS и Mac OS X) свой формат — Mach-O (сокращение от Mach object).

Формат в Windows

В Windows исполняемые файлы имеют расширение exe.

Формат исполняемых файлов называется PE (Portable Executable). Первые два байта PE файла содержат сигнатуру 0x4D 0x5A — «MZ». PE представляет собой модифицированную версию COFF (Common Object File Format) формата файла для UNIX. PE/COFF — альтернативный термин при разработке Windows.

Сборка простейшей программы

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

На выходе a.out — исполняемый файл.

На выходе main.exe — исполняемый файл.

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

Стадии

Классический сценарий сборки кода на C включает четыре этапа.

Препроцессинг

О препроцессоре мы говорили в прошлый раз. Он подставляет include-файлы, генерирует код с помощью макросов, заменяет define-константы на их значения.

Посмотреть результат препроцессинга можно через

Результат выводится на стандартный вывод.

Вывод может быть большим. Например, на этот 7-строчный файл gcc 5.4.0 генерирует 854 строки. Если инклудов много, там будут тысячи строк.

Компиляция

Обработка исходного кода (уже без директив препроцессора) и преобразование его в команды ассемблера для целевой платформы (например x86).

Результат записывается в файл main.s.

Ассемблирование

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

Результат записывается в файл main.o.

Компоновка (linking)

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

Диаграмма

GCC-CompilationProcess.png

Сборка программ из нескольких C-файлов

На практике сегодня прероцессор, компилятор и ассемблер обычно не разделяют, это одна программа, и представление об отдельных трёх стадиях полезно лишь теоретически. Для простоты будем называть компиляцией получение из C-файла объектного файла.

Каждый C-файл можно компилировать независимо в свой объектный файл, а затем компоновать.

Это даёт следующие преимущества.

  • Разные C-файлы компилируются параллельно на разных ядрах процессора.
  • Если изменяется один C-файл, достаточно перекомпилировать только его.

Функции и переменные в C

Объявление и определение

Необходимо понимать разницу между объявлением и определением.

Определение (definition) связывает имя с реализацией:

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

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

Заметьте, что определение также является объявлением — фактически это объявление, в котором «другое место» программы совпадает с текущим.

Классификация

Интуитивно понятным является понятие области видимости переменной (scope):

  1. глобальные переменнные,
  2. локальные переменные.

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

Мы уже рассматривали три класса хранения (storage class):

  1. статический,
  2. автоматический,
  3. динамический.

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

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

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

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

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

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

Работа компилятора C заключается в конвертировании текста программы на C, понятного человеку, в нечто, что понимает компьютер. На выходе компилятор выдаёт объектный файл.

Содержание объектного файла — в сущности, две вещи:

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

Код и данные будут иметь ассоциированные с ними имена — имена функций или переменных, с которыми они связаны определением.

Объектный код — это последовательность машинных инструкций для процессора, которая соответствует C-коду: if‘ы и while‘ы и пр. Эти инструкции должны манипулировать информацией определённого рода, а информация должна где-нибудь находится — для этого нам и нужны переменные. Код может также ссылаться на другой код (в частности, на другие C-функции в программе).

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

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

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

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

Linker1.png

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

Полезно посмотреть, как это работает на практике.

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

На UNIX объектные файлы имеют расширение o. Формат — ELF.

На Windows используется расширение obj. Формат — COFF.

На платформе UNIX основным инструментом для нас будет команда nm, которая выдаёт информацию о символах объектного файла. Для Windows команда dumpbin с опцией /symbols является приблизительным эквивалентом. Также есть портированные под Windows инструменты GNU binutils, которые включают nm.exe.

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

Результат может выглядеть немного по разному на разных платформах.

Ключевыми сведениями являются класс каждого символа и его размер (если присутствует).

  • Класс U (от undefined) обозначает неопределённые ссылки, те самые «пустые места», упомянутые выше. Для этого класса существует два объекта: fn_a и z_global.
  • Классы t и T (от слова text) указывают на код, который определён; различие между t и T заключается в том, является ли функция статической (t) (локальной в файле) или нет (T), т.е. была ли функция объявлена как static.
  • Классы d и D (от слова data) содержат инициализированные глобальные переменные. При этом статические переменные принадлежат классу d.
  • Для неинициализированных глобальных переменных мы получаем b, если они статичные, и B или C иначе.

Сегменты и секции

В формате ELF есть понятие сегмента и секции.

Понятие сегмента из ELF-файла не имеет ничего общего с сегментацией памяти в архитектуре x86, с сегментными регистрами и дескрипторами процессора. Здесь сегмент — это просто диапазон адресов в памяти.

Секция включает информацию, используемую в процессе компоновки. В частности, имена символов, сами данные переменных и машинные инструкции.

Сегмент — группа секций. Сегмент описывается заголовком (program header). В одном сегменте 0 или более секций. Сегмент также содержит информацию, необходимую для исполнения файла (runtime). Куда необходимо загрузить данные в виртуальном адресном пространстве процесса, какие права установить на эти страницы памяти (чтение, запись, исполнение). Мы это более детально будем изучать применительно к архитектуре x86 на последующих занятиях.

Секция кода или секция текста содержит исполняемые инструкции для процессора. Как правило, доступна только для чтения.

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

.rodata

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

Читать:
Что означает год внизу сайта

Название BSS объясняется историческими причинами. Изначально это сокращение от названия операции «Block Started by Symbol» на древней ЭВМ IBM 704 (1957 г.). Некоторые расшифровывают эту аббревиатуру как «Better Save Space».

Представление программы в памяти

LinuxFlexibleAddressSpaceLayout.png

Что делает компоновщик

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

Linker2.png

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

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

Linker3.png

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

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

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

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

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

Однако, компоновщики должны уметь обходится также и с другими языками кроме C и C++, для которых правило одного определения не обязательно соблюдается.

Библиотеки

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

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

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

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

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

В системах UNIX командой для сборки статичной библиотеки обычно является ar, и библиотечный файл, который при этом получается, имеет расширение *.a. Также эти файлы обычно имеют префикс «lib» в своём названии и они передаются компоновщику с опцией «-l» с последующим именем библиотеки без префикса и расширения (т.е. «-lfred» подхватит файл «libfred.a»).

В системе Windows статические библиотеки имеют расширение .LIB и собираются инструментами LIB, однако этот факт может ввести в заблуждение, так как такое же расширение используется и для «import library», которая содержит в себе только список того, что имеется в DLL — рассмотрим позже.

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

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

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

Приведём пример, чтоб прояснить ситуацию; предположим у нас есть следующие объектные файлы и строка команды компоновки, которая содержит

Файл a.o b.o libx.a liby.a
Объект a.o b.o x1.o x2.o x3.o y1.o y2.o y3.o
Определения a1, a2, a3 b1, b2 x11, x12, x13 x21, x22, x23 x31, x32 y11, y12 y21, y22 y31, y32
Неразрешённые ссылки b2, x12 a3, y22 x23, y12 y11 y21 x31

Как только компоновщик обработал a.o и b.o, ссылки на b2 и a3 будут разрешены, в то время как x12 и y22 будут всё ещё неразрешёнными. В этот момент компоновщик проверяет первую библиотеку libx.a на наличие недостающих символов и находит, что он может включить x1.o, чтобы компенсировать ссылку на x12; однако, делая это, x23 и y12 добавляются в список неопределённых ссылок (теперь список выглядит как y22, x23, y12).

Компоновщик всё ещё имеет дело с libx.a, поэтому ссылка на x23 легко компенсируется, включая x2.o из libx.a. Однако это добавляет y11 к списку неопределённых (который стал y22, y12, y11). Ни одна из этих ссылок не может быть разрешена использованием libx.a, таким образом, компоновщик принимается за liby.a.

Здесь происходит примерно то же самое, и компоновщик включает y1.o и y2.o. Первым объектом добавляется ссылка на y21, но так как y2.o всё равно будет включено, эта ссылка разрешается просто. Результатом этого процесса является то, что все неопределённые ссылки разрешены, и некоторые (но не все) объекты библиотек включены в конечный исполняемый файл.

Заметьте, что ситуация несколько изменяется, если, скажем, b.o тоже имел бы ссылку на y32. Если это было бы так, то компоновка libx.a происходила бы так же, но обработка liby.a повлекла бы включение y3.o. Включением этого объекта мы добавим x31 к списку неразрешённых символов и эта ссылка останется неразрешённой — на этой стадии компоновщик уже завершил обработку libx.a и поэтому уже не найдёт определение этого символа (в x3.o).

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

Динамические библиотеки

Будем рассматривать на следующих занятиях.

Язык C++

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

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

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

Такое положение вещей определённо затрудняет работу компоновщика: если какой-нибудь код обращается к функции max, какая именно имелась в виду?

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

Схемы отличаются от платформы к платформе.

Здесь мы видим три функции max, каждая из которых получила отличное имя в объектном файле, и мы можем проявить смекалку и предположить, что две следующие буквы после «max» обозначают типы входящих параметров — «i» как int, «f» как float и «d» как double (однако всё значительно усложняется, если классы, пространства имён, шаблоны и перегруженные операторы вступают в игру).

Также стоит отметить, что обычно есть способ конвертирования между именами, видимых программисту, и именами, видимых компоновщику. Это может быть и отдельная программа (например, c++filt) или опция в командной строке (например --demangle для GNU nm), которая выдаёт что-то похожее на это:

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

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

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

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

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

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

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

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

Шаблоны

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

C++ вводит понятия шаблона (templates), который позволяет использовать код, приведённый ниже, сразу для всех случаев.

Этот написанный на C++ код использует max<int>(int,int) и max<double>(double,double). Однако, какой-нибудь другой код мог бы использовать и другие инстанции этого шаблона.

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

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

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

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

5 ответов

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

компоновщик берет все эти объектные файлы и объединяет их в один исполняемый файл (предполагая, что он может, т. е. что нет дубликатов или неопределенных символов). Большое компиляторы сделайте это для вас (читайте: они запускают компоновщик самостоятельно), если вы не скажете им «просто скомпилировать», используя параметры командной строки. ( -c является общей опцией» просто компилировать; не связывать».)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

если у вас a.c исходный файл, чтобы создать его объектный файл с помощью GCC, вы должны запустить: gcc a.c -c

полный процесс будет: препроцессор (cpp) будет работать над a.c. Его выход (все еще источник) будет подаваться в компилятор (СС1). Его выход (сборка) будет подаваться в ассемблер (as), который будет производить relocatable object file . Этот файл содержит объектный код и ссылку (и может отлаживать, если -g был использован) метаданные, и не является непосредственно исполняемым.

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

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

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

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

результат запуска компоновщика над несколькими relocatable object files это executable object file . Компоновщик объединяет все входные объектные файлы из командной строки слева направо, объединяя все входные секции одного типа (например, .data ) к тому же типу выходной секции. Он использует symbol resolution и relocation .

бонус:

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

процесс соединения можно сделать следующим образом: ld a.o -o myexecutable

в команда: gcc a.c -o myexecutable вызовет все команды, упомянутые в пункте 1 и в пункте 3 (cpp — > cc1 — > as — > ld 1 )

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

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

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

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

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

Похожие статьи