Как скомпилировать программу из нескольких файлов

Всем, доброго утречка, с вами Мирра Андрюхан. И насчет прошлой статьи в данном цикле статей, могу заметить, что пример можно собрать и на компиляторе Си++, поменяв «gcc» на «g++». Но, чтобы нормально программировать на Си++, рекомендую ознакомиться и попрактиковаться с данной статьей. Т.к. в свое время я тоже кое-что делал и садился в лужу, когда не мог понять, где ошибка в программе и почему программа не может собраться из нескольких файлов. А, все нормальные люди пишут интересные программы разбивая их на несколько файлов.

Для начала, давайте посмотрим на то, как можно собрать пример из прошлой статьи (см. ниже), причем оба способа делают в конечном итоге исполняемый файл, который пишет нам «Привет, Мир!» после своего запуска. Причем прошлый способ — это взаимодействие посредством командной строки с компилятором напрямую одной командой, так легко собирать примеры и задачки из учебника по Си++ для школьников и студентов.

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

Python в EXE

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

Первый вариант makefile
# Имя программы: programma (Программа-пример «Привет, Мир!»)
# Авторское право (c) 2022 Смирнов Андрей Владимирович известный, как Мирра Андрюхан
# Лицензия: GNU LGPL 2.1
# Веб-сайт: http://andryuhan.ru
programma : programma . o
g сс programma . o — o programma ; rm * . o
programma . o : programma . c
g сс — c programma . c
Второй вариант makefile
# Имя программы: programma (Программа-пример «Привет, Мир!»)
# Авторское право (c) 2022 Смирнов Андрей Владимирович известный, как Мирра Андрюхан
# Лицензия: GNU LGPL 2.1
# Веб-сайт: http://andryuhan.ru
final : programma clean
programma : programma . o
g сс programma . o — o programma
programma . o : programma . c
g сс — c programma . c

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

Makefile. Компиляция нескольких файлов с исходным кодом

Теперь, давайте пройдемся по задачам из двух примеров файла «makefile». Задача «programma» имеет условие «programma.o«, которое необходимо выполнить перед выполнением данной задачи, т.е. команда строкой ниже, будет выполнена после выполнения задачи «programma.o«.

Разумеется у задачи «programma.o» есть тоже условие, но оно указывает на наличие файла (ведь такой задачи нет, поэтому ищет файл) «programma.c», который обязан быть в рабочей директории рядом с файлом «makefile». При этом, задача «programma.o» создает файл «programma.o» в той же директории из исходника «programma.c», а после данный объектный файл применяется для изготовления исполняемого файла в задаче «programma«. Для второго примера файла «makefile», выполнение закончится выполнением задачи «final«, которая помимо вышеописанного еще и вызовет задачу без условий для выполнения «clean«, которая удалит ненужные объектные файлы, применявшиеся в изготовлении исполняемого файла программы. Однако, если второму варианту задать задачу «programma«, то задача «final» будет проигнорирована, т.к. ниже она нигде не упоминается, т.е. есть это вариант вызова «make» с конкретной задачей.

Читайте также:
О программе черный список

Запускать выполнение «make», по вышеописанному примеру можно следующим способом.

Варианты запуска make
make final
make programma

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

Запуск собранной программы

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

Исходный код файла vtoroifile.c
//Сама функция «Привет, Мир!»
void privetmir ( ) <
char stroka [ ] = «Привет, Мир!n» ;
printf ( «%s» , stroka ) ;
Исходный код файла programma.c
#include «vtoroifile.c»
//Вызываем функцию «Привет, Мир!».
//Возвращаем ноль, чтобы не возвращать кодов ошибки.
Файл makefile
finalsborki : programma clean
programma : programma . o
gcc programma . o — o programma
programma . o : programma . c vtoroifile . c
gcc — c programma . c vtoroifile . c

Если обратите внимание, на задачу «programma.o«, то увидите, что она дополнилась файлом «vtoroifile.c», который содержит функцию «privetmir», выполняющую, что и пример из прошлой статьи (см. ссылку в начале статьи), а также условием выполнения на наличие этого файла в рабочей директории. Т.е. в данном случае, были собраны все необходимые файлы для компиляции из исходного кода в один объектный файл, а потом из него в исполняемый файл программы. Как видите, ничего сложного в этом нет.

При этом, вы можете данную программу собрать на компиляторе Си++, для этого достаточно в файле «makefile» поменять везде «gcc» на «g++».

А теперь, возьмемся за пример сбора нескольких файлов для Си++, на примере создания объекта из класса PrivetMir, который будет прописан в двух файлах «privetmir.cpp» и «privetmir.h». Итог выполнения программы будет такой же.

Исходный код privetmir.h
//Класс «Привет, Мир» (PrivetMir)
class PrivetMir
//Конструктор класса
Исходный код privetmir.cpp
#include «privetmir.h»
//Конструктор класса PrivetMir
PrivetMir :: PrivetMir ( )
char stroka [ ] = «Привет, Мир!n» ;
printf ( «%s» , stroka ) ;
Исходный код programma.cpp
#include «privetmir.h»
//Вызываем конструктор класса «Привет, Мир!», при инициализации переменной.
PrivetMir privetmir ;
//Возвращаем ноль, чтобы не возвращать кодов ошибки.
Исходный код makefile
finalsborki : programma clean
programma : programma . o privetmir . o
g ++ programma . o privetmir . o — o programma
programma . o : programma . cpp
g ++ — c programma . cpp
privetmir . o : privetmir . cpp privetmir . h
g ++ — c privetmir . cpp

В общем смысл программы тот же, а суть такова. Т.е., в простом классе «PrivetMir«, в его конструкторе выводится все та же строка «Привет, Мир!». А, сам класс прописан, в привычной форме для Си++, в виде двух файлов. Это наглядный пример, того как изготавливается исполняемый файл программы через «make«. А теперь, рассмотрим его подробнее.

Файл «privetmir.h» является заголовочным файлом в языке Си/Си++, в нем объявляются все классы и переменные, а также прописываются дополнительные заголовочные файлы, нужные для работы класса «PrivetMir«. В данном примере применяется язык Си++, т.к. мы применяем классы, а в Си нет ООП, т.е. объектно ориентированного программирования. И, остальное пока оставим без объяснений, т.к. этот пример не про классы и ООП в языке Си++, а про сборку программы из нескольких файлов исходного кода.

В файле «makefile» вы наверняка заметили, что появилась задача «privetmir.o«, а также она стала дополнительным условием для задачи «programma» и командная строка для выполнения также дополнилась ее объектным файлом. При этом, заметьте, что у задачи «privetmir.o«, тоже есть дополнительное условие для выполнения, которое указывает на файл исходного кода «privetmir.h«.

При этом, обращая внимание на команды для командной строки, представленные в задачах, могу сказать следующие. В задачах «privetmir.o» и «programma.o» идет компиляция исходного кода и перевод его в данные объектных файлов, а уже потом в задаче «programma» происходит связывание файлов между собой в исполняемый файл. У иностранцев это называется процессом «линковки» (т.е. на их слове «link» по нашему «связь»). На данном этапе формируется исполняемый файл, который потом может выполнятся ОС Линукс после его запуска.

Для простоты понимания самого процесса сборки посмотрите на диаграмму ниже.

Из диаграммы выше видно, что программой «make» сначала компилируется исходники класса «PrivetMir«, потом компилируется исходник самой программы. После компиляции исходных файлов программы, производится линковка, где применяется файл стандартной библиотеки «stdio» (он применяется в классе «PrivetMir»). И все собирается в конечный, исполняемый файл.

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

Читайте также:
Показатели эффективности разработки программы

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

А пока, поздравляю вас с тем, что вы теперь знаете и в малой мере понимаете, как собирать программу из нескольких файлов программой «make» в ОС ГНУ/Линукс. А также, теперь вы сможете собирать программы различной сложности, что потом нам с вами пригодится для создания программы работающей с командной строкой.

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

Источник: andryuhan.ru

Как скомпилировать программу из нескольких файлов

Команда gcc предназначена для компиляции с помощью компилятора GCC кода на языке C. Данная команда похожа на команду g++, используемую для компиляции кода на языке C++.

Базовый синтаксис команды выглядит следующим образом:

$ gcc [параметры] имена файлов

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

Примеры использования

Компиляция программы из одного файла исходного кода

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

Это содержимое файла исходного кода test.c:

Источник: linux-faq.ru

Как скомпилировать программу из нескольких файлов

Язык С был создан для того, чтобы можно было использовать одну операционную систему для разных машин. Около 1970 г. Кен Томпсон и Деннис Ритчи, сотрудники компании Bell Labs, решили создать ОС Unix, которая писалась на языке С и была предназначена для удобного программирования на данном языке. С тех пор он был портирован на многие другие ОС и стал одним из самых используемых языков программирования.

Рассмотрим плюсы и минусы языка С

Положительные качества довольно очевидны: это язык высокого уровня, настолько эффективный, насколько это возможно. Но так как язык создавался давно, то образец, по которому он создавался, уже устарел, но менять его нельзя, иначе придется переписывать большинство ПО.
Этот минус можно увидеть на примере: при программировании на данном языке стандартной ошибкой является запись if ( A=0) вместо if (A == 0) , которая не отслеживается компилятором и приводит к результату, которого не ожидал программист.Вместо того, чтобы сделать проверку, равно ли А нулю, мы присвоили А значение нуля и условие в If всегда будет ложью. Многие вещи в С сделаны для эффективной работы машины, а не для удобства программиста.

В 1980 году Страуструп решил выпустить расширение языка С. Его назвали его C with classes, но вскоре его переименовали в С++. Так как это расширение С, то большинство минусов С перешло в новый язык, только несколько функций С не принадлежат С++. Это расширение позволяет более удобно писать большие программы.

Как из кода получается программа (С)

Минимальная программа

Рассмотрим простую программу

int main()

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

Читайте также:
Программа Андроид с которой может быть оффлайн

Программа из нескольких файлов


Теперь рассмотрим простую программу вывода Hello.
void hello() < printf(«Hello!n»); >int main()

Чтобы скомпилировать эту программу в Unix, надо написать в командной строке следующие команды:
gcc main.c
Эта команда создает файл a.out. Не очень удобно, если все файлы будут названы одним именем, поэтому лучше писать:
gcc main.c -o program , тогда именем файла будет program.
Научившись создавать простые программы, научимся создавать программу из двух файлов. Запишем функцию hello() в один файл hello.c, а функцию main() в файл main.c.
Чтобы сделать из этого программу следует написать в командной строке
gcc hello.c main.c — o program
Чтобы запустить получившуюся программу, надо ввести в командной строке:
./program
А чтобы скомпилировать их по отдельности:
gcc -c hello.c //на выходе будет файл hello.о, -с означает, что мы только компилируем данную программу, hello.o называется объектным кодом.
gcc -c main.c // на выходе будет файл main.о
gcc hello.o main.o — o program // этот этап называется linking

Компиляция и сборка


Рассмотрим подробнее этот процесс.

В первых двух командах мы просто компилируем программы. Компиляция — перевод текста программы в машинный код и оптимизация его. Вместо функции hello() в main будет написано просто «вызвать функцию hello».

Третья команда называется Linking( или сборка). В процессе выполнения её вместо «вызвать функцию hello» будет написано «вызвать функцию по адресу». Очевидно, что большая часть работы выполняется при компиляции.

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

Потенциальная проблема с числом параметров

Предположим, что мы решили в функцию hello добавить аргумент, но в функции main забыли передать функции hello значение.

—файл hello.c—- void hello(int n) < printf(«%dn»,n); >—файл main.c—- int main()

При раздельной компиляции ни одна из команд gcс, учавствующих в раздельной компиляции этой программы, не выдаст нам ошибки. По отдельности эти файлы компилируются правильно. Чтобы избежать эту ошибку, необходимо в каждом файле объявить все функции. Тогда при компиляции файла hello.c компилятор будет сверять количество параметров в объявлении и определении, а в файле main.c компилятор будет сравнивать количество параметров в вызове и объявлении:

—файл hello.с—- void hello(int n); // объявление функции hello(int n) void hello(int n) < printf(«%dn»,n); >—файл main.с—- void hello(int n); int main()

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

Заголовочный файл

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

—файл header.h— void hello(int); —файл hello.с—- #include «header.h» //include просто заменяет при preprocessing данную строку на содержимое файла header.h void hello(int n) < printf(«%dn»,n); >—файл main.с—- #include «header.h» int main()

Если написать определение функции hello в header.h, то выдадут ошибку, т.к. будет дважды определена одна функция. Ошибку выдадут при linking.

Обратим внимание, что у нас в программе есть ещё одна функция — printf, которая является функцией стандартной библиотеки. По причинам, озвученным выше, стоит подключать ещё и header стандартной библиотеки: #include (<>- данные скобки означают, что header надо взять из места, где лежат стандартные headers)

Многократное включение


Последнее на данное занятие: ошибка зацикливания headers.Пример:

—файл first.h—- #include «second.h» —файл second.h—- #include «first.h»

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

—файл first.h—- #if ndef _FIRST_H_ // если макрос ещё не определен,то #define _FIRST_H_ // определяем его #include «second.h» . #endif —файл second.h—- #if ndef _Second_H_ #define _Second_H_ #include «first.h» . #endif

Рассмотрим по порядку эту программу. Запускаем first.h, макрос _FIRST_H ещё не определен, значит определяем его и переходим к second.h. Макрос _Second_H_ ещё не определен, значит определяем его и переходим к first.h. Макрос _FIRST_H уже определен, значит идем за endifтут конец файла. Проблема зацикливания решена.

Источник: amse.ru

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru