Чем компиляция отличается от сборки

Сборка или компиляция

У меня есть теоретический вопрос о разнице между компиляцией и сборкой. Я программирую в проекте c ++, сборка которого занимает много времени, поэтому я сказал делать сборку только в тех случаях, когда «я изменил любой заголовочный файл». Это правда? Если я добавлю новый атрибут в файл заголовка, нужно ли мне его строить? Не достаточно компиляции?

Решение

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

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

Другие решения

составление это превращение исходного кода в объектный код.

соединение это процесс объединения объектного кода с библиотеками в сырой исполняемый файл.

Строительство это последовательность, составленная из компиляции и компоновки, возможно, с другими задачами, такими как создание установщика.

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

Компиляция — это процесс преобразования кода высокого уровня в код машинного уровня

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

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

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

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

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

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

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

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

Я не уверен, что полностью понял ваш вопрос.

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

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

В чем разница между сборкой и компиляцией?

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

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

«Компиляция» более специфична и почти всегда относится к процессу, который принимает исходный код в качестве входных данных и выводит что-то работоспособное, обычно машинный код для физической или виртуальной машины, или исходный код на другом языке.

Эти термины часто используются взаимозаменяемо, но я бы выделил их следующим образом:

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

Таким образом, компиляция действительно подмножество сборки.

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

Например. в C ++ для создания проекта необходим препроцессор (предварительная обработка исходных файлов); компилятор (компиляция исходных файлов); компоновщик (объединение всего в исполняемый файл — скомпилированный код, значки, строки, другие ресурсы вместе)

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

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). Однако, какой-нибудь другой код мог бы использовать и другие инстанции этого шаблона.

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

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

Сборка и компиляция (Java)

Думая, что ответ на это довольно очевиден, но вот оно:

Когда я работаю над небольшим школьным проектом (на java), я компилирую его.

В моем курятнике мы используем ant для сборки нашего проекта.

Я думаю, что компиляция — это часть построения. Это правильно? В чем разница между сборкой и компиляцией?

8 ответов

«Сборка» — это процесс, который охватывает все шаги, необходимые для создания «конечного продукта» вашего программного обеспечения. В мире Java это обычно включает:

  1. Генерация источников (иногда).
  2. Компиляция исходников.
  3. Сборка тестовых исходников.
  4. Выполнение тестов (модульные тесты, интеграционные тесты и т. Д.).
  5. Упаковка (в баночку, вар, эйб-банку, ушко).
  6. Выполнение проверок работоспособности (статические анализаторы, такие как Checkstyle, Findbugs, PMD, покрытие тестами и т. Д.).
  7. Формирование отчетов.

Итак, как видите, компиляция — это только (небольшая) часть сборки (и лучше всего полностью автоматизировать все шаги с помощью таких инструментов, как Maven или Ant, и непрерывно запускать сборку, что известно как Непрерывная интеграция).

Некоторые из ответов, которые я вижу здесь, вырваны из контекста и имеют больше смысла, если бы это был вопрос C / C ++.

  • «Компиляция» превращает файлы .java в файлы .class.
  • «Сборка» — это общий термин, который включает компиляцию и другие задачи.

«Сборка» — это общий термин, описывающий общий процесс, который включает компиляцию. Например, процесс сборки может включать инструменты, которые генерируют код Java или файлы документации.

Часто будут присутствовать дополнительные этапы, такие как «пакет», который берет все ваши файлы .class и помещает их в .jar, или «чистый», который очищает файлы .class и временные каталоги.

Компиляция — это процесс преобразования исходного кода в объектный код.

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

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

Многие компиляторы автоматически обрабатывают этап связывания после компиляции исходного кода.

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

Сборка объединяет все скомпилированные части и создает (строит) исполняемый файл.

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

В Java : сборка — это жизненный цикл, содержащий последовательность именованных фаз.

Например: maven имеет три жизненных цикла сборки, следующий — default жизненный цикл сборки.

На самом деле вы делаете то же самое. Ant — это система сборки, основанная на файлах конфигурации XML, которая может выполнять широкий спектр задач, связанных с компиляцией программного обеспечения. Компиляция вашего java-кода — лишь одна из этих задач. Есть много других, таких как копирование файлов, настройка серверов, сборка zip-архивов и jar-файлов, а также компиляция других языков, таких как C.

Вам не нужен Ant для компиляции вашего программного обеспечения. Вы можете сделать это вручную, как в школе. Еще одна альтернатива Ant — продукт под названием Maven. И Ant, и Maven делают одно и то же, но совершенно по-разному.

Найдите Ant и Maven для получения дополнительных сведений.

В Eclipse и IntelliJ процесс сборки состоит из следующих шагов: очистка предыдущих пакетов, подтвердить, компилировать контрольная работа, пакет,
интеграция проверять, установить, развертывать.

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

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

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