Как сделать объектный файл gcc

Обновлено: 05.07.2024

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

Для того, чтобы вынести функцию или переменную в отдельный файл надо перед ней поставить зарезервированное слово extern. Давайте для примера создадим программу из нескольких файлов. Сначала создадим главную программу, в которой будут две внешние процедуры. Назовем этот файл main.c:

Теперь создаем два файла, каждый из которых будет содержать полное определение внешней функции из главной программы. Файлы назовем f1.c и f2.c:

После этого процесс компиляции программы с помощью gcc будет выглядеть несколько иначе от описанного в "Шаг 1 - Компиляция программ на языке C/C++".

Компилировать можно все файлы одновременно одной командой, перечисляя составные файлы через пробел после ключа -c:

Или каждый файл в отдельности:

В результате работы компилятора мы получим три отдельных объектных файла:

Чтобы их собрать в один файл с помощью gcc надо использовать ключ -o, при этом линкер соберет все файлы в один:

В результате вызова полученной программы rezult командой:

На экране появится результат работы:

Теперь, если мы изменим какую-то из процедур, например f1():

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

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

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

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

Для этого создадим каталог (папку, директорий) где-нибудь, например, с именем MYPROG01 и создадим в данном каталоге файл с именем, например, main.c, так как файлы с исходным кодом принято создавать с расширением .c. Файл мы создадим вот такого содержания

Это и будет наша первая программа, которая выведет в консоль текст Hello, World. , а также возвратит каретку и переведёт строку благодаря специальным символам 'r' и 'n'.

Что же находится в нашем исходном коде.

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

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

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

Задача MinGW — преобразовать файл с исходным кодом в исполняемый типа exe.

Откроем консоль (командную строку), находясь в нашем каталоге и, оказывается для нашей операции вполне достаточно одной команды gcc, в параметрах которой мы указываем файл с исходным кодом


В случае успеха у нас появится файл с именем a.exe в текущем каталоге


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


Отлично! Наша программа работает.

Также мы можем назначить любое имя для выходного файла в параметре с помощью ключа -o (расширение можно также не вводить). Удалим a.exe и введём команду


У нас появится файл с нужным нам именем



Работоспособность его будет такая же, так как это тот же файл, только с другим именем


Как вообще происходит процесс создания исполняемого файла из файла (файлов) с исходным кодом?

Первый этап — это трансляция, которая превращает исходный код в объектный, и создаётся файл (или несколько файлов) с расширением -o.

Второй этап — это компоновка или линковка. На данном этапе объектный файл (или несколько) компонуется (компонуются) в один исполняемый файл.

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

Удалим файл myprog.exe, чтобы у нас в директории остался лишь файл main.c.

Затем введём вот такую команду, которая нам сформирует файл main.i со всем кодом, в т.ч. подключенным, в результате препроцессинга


Судя по размеру полученного файла, мы понимаем, сколько кода нам в него добавил препроцессор


Покажу лишь часть кода в этом файле, остальной посмотрите у себя

Далее мы из этого файла можем получить ассемблерный (main.s) с помощью следующей команды


Откроем данный файл и посмотрим его содержимое

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

Теперь непосредственно компиляция. Для создания объектного файла main.o дадим следующую команду


Имя выходного файла мы не указываем, так как по умолчанию формируется файл с тем же именем, что и исходный, если нужно какое-то другое имя на выходе, то можно использовать ключ -o.

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


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

Затем мы уже производим линковку (создаём исполняемый файл из объектного (или нескольких))с помощью следующей команды


После данной команды у нас появился исполняемый файл


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

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

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

Я пробовал различные уровни в случае формирования исполняемого кода из исходного файла нашего занятия. Результат мало чем отличается, так как у нас и кода-то особого нет. А в случае написания программ с огромным количеством кода оптимизация сильно помогает. Хотя всю отладку программ на этапе их создания и тестирования лучше производить с отключенной оптимизацией (без использования ключа, так как по умолчанию оптимизация отключена, либо с ключом -O0).

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

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

Обзор компиляторов

Существует множество компиляторов с языка C++, которые можно использовать для создания исполняемого кода под разные платформы. Проекты компиляторов можно классифицировать по следующим критериям.

  1. Коммерческие и некоммерческие проекты
  2. Уровень поддержки современных тенденций и стандартов языка
  3. Эффективность результирующего кода

Если на использование коммерческих компиляторов нет особых причин, то имеет смысл использовать компилятор с языка C++ из GNU коллекции компиляторов (GNU Compiler Collection). Этот компилятор есть в любом дистрибутиве Linux, и, он, также, доступен для платформы Windows как часть проекта MinGW (Minumum GNU for Windows). Для работы с компилятором удобнее всего использовать какой-нибудь дистрибутив Linux, но если вы твердо решили учиться программировать под Windows, то удобнее всего будет установить некоммерческую версию среды разработки QtCreator вместе с QtSDK ориентированную на MinGW. Обычно, на сайте производителя Qt можно найти инсталлятор под Windows, который сразу включает в себя среду разработки QtCreator и QtSDK. Следует только быть внимательным и выбрать ту версию, которая ориентирована на MinGW. Мы, возможно, за исключением особо оговариваемых случаев, будем использовать компилятор из дистрибутива Linux.

GNU коллекция компиляторов включает в себя несколько языков. Из них, группу языков Си составляет три компилятора.

  1. g++ — компилятор с языка C++.
  2. gcc — компилятор с языка C (GNU C Compiler).
  3. gcc -lobjc — Objective-C — это, фактически, язык C с некоторой макро-магией, которая доступна в объектной библиотеке objc. Ее следует поставить и указать через ключ компиляции -l.

Этапы компиляции

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

  1. Препроцессинг — обработка текстовых файлов утилитой препроцессора, который производит замены текстов согласно правилам языка препроцессора C/C++. После препроцессора, тексты компилируемых файлов, обычно, значительно вырастают в размерах, но теперь в них содержится все, что потребуется компилятору для создания объектного файла.
  2. Ассемблирование — процесс превращения текста на языке C++ в текст на языке Ассемблера. Для компиляторов GNU используется синтаксис ассебмлера AT&T.
  3. Компилирование — процесс превращения текстов на языке Ассемблера в объектные файлы. Это файлы состоящие из кодов целевого процессора, но в которых еще не проставлены адреса объектов, которые находятся в других объектных файлах или библиотеках.
  4. Линковка — процесс объединения объектных файлов проекта и используемых библиотек в единую целевую сущность для целевой платформы. Это может быть исполняемая программа или библиотека статического или динамического типа.

Рассмотрим подробнее упомянутые выше стадии обработки текстовых файлов на языке C++.

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

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

На входе препроцессора мы имеем исходный файл с текстом на языке C++ включающим в себя элементы языка препроцессора.

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

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

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

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

Компиляция

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

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

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

Линковка

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

Средства сборки проекта

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

  1. GNU Toolchain — Старейшая система сборки проектов известная еще по сочетанию команд configure-make-"make install".
  2. CMake — Кроссплатформенная система сборки, которая позволяет не только создать кроссплатформенный проект но и создать сценарий компиляции под любые известные среды разработки, для которых написаны соответствующие генераторы сценариев.
  3. QMake — Достаточно простая система сборки, специально реализованная для фреймворка Qt и широко используемая именно для сборки Qt-проектов. Может быть использована и просто для сборки проектов на языке C++. Имеет некоторые проблемы с выявлением сложных зависимостей метакомпиляции, специфической для Qt, поэтому, даже в проектах Qt, рекомендуется использование системы сборки CMake.

Современные версии QtCreator могут работать с проектами, которые используют как систему сборки QMake, так и систему сборки CMake.

Простой пример компиляции

Рассмотрим простейший проект "Hello world" на языке C++. Для его компиляции мы будет использовать консоль, в которой будем писать прямые команды компиляции. Это позволит нам максимально прочувствовать описанные выше этапы компиляции. Создадим файл с именем main.cpp и поместим в него следующий текст программы.

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

В третьей строке программы описана функция main(). В контексте операционной системы, каждое приложение должно иметь точку входа. Такой точкой входа в операционных системах *nix является функция main(). Именно с нее начинается исполнение приложения после его загрузки в память вычислительной системы. Так как операционная система Windows имеет корни тесно переплетенные с историей *nix, и, фактически, является далеким проприентарным клоном *nix, то и для нее справедливо данное правило. Поэтому, если вы пишете приложение, то начинается оно всегда с функции main().

При вызове функции main(), операционная система передает в нее два параметра. Первый параметр — это количество параметров запуска приложения, а второй — строковый массив этих параметров. В нашем случае, мы их не используем.

В пятой строке мы обращаемся к предопределенному объекту cout из пространства имен std, который связан с потоком вывода приложения. Используя синтаксис операций, определенных для указанного объекта, мы передаем в него строку "Hello world" и символ возврата каретки и переноса строки.

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

Следующим шагом проведения эксперимента выполним останов компиляции файла main.cpp после этапа ассемблирования. Для этого воспользуемся ключом -S для компилятора g++. Здесь и далее, знак доллара ($) обозначает стандартное приглашение к вводу команды в консоли *nix. Писать знак доллара не требуется.

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

Для остановки компиляции после, собственно, компиляции следует воспользоваться ключом -c для компилятора g++.

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

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

нет архиве, но другой вариант Unity строит.

создайте отдельный "файл unity"

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

можно использовать ld -r чтобы объединить объекты, сохраняя информацию о перемещении и оставляя конструкторы неразрешенными:

вы можете создать archive который представляет собой набор объектных файлов.

так эффективно вы совместили file1.cpp и file2.cpp на mylib.a .

вы можете использовать ar команда для создания архива для использования вашей программой.

использование решения из Питер Александр Главная, что приходит на ум.

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

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

Я знаю, что вы спрашиваете о том, как совместить .cpp-файлы в один объектный файл. Я предполагаю, что ваша цель для этого-связать объединенный объект позже с другими отдельными объектными файлами. Если это так,вы можете использовать статическую или динамическую библиотеку. Для ваших целей я предлагаю динамическую библиотеку, так как вы можете сделать один шаг компиляции и ссылки, пропустив генерацию объектных файлов. Использование такой команды, как g++ -shared -fpic file1.cpp file2.cpp file3.cpp -o libtest.so вы можете комбинировать ваши .cpp-файлы, которые нужно объединять в библиотеки. Для компиляции библиотеки (библиотек) с отдельными объектными файлами используйте команду g++ -ltest individual1.o individual2.o individual3.o -o myexecutable . В этом последнем шаге связывания я предполагаю libtest.so в вашем текущем каталоге. Если его нет в вашем текущем каталоге, добавьте -L флаг и каталогов libtest.so в.

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

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

мы используем опцию-o для создания объектного файла. По умолчанию при компиляции файла с помощью gcc или G++ мы получаем объектный файл с именем a.но мы можем изменить его название. используйте following для компиляции файла

Читайте также: