Немного о программе make

Очередная лабораторная по системному программированию дала мне идею для написания этой статьи. Хотя непосредственно с системным программированием утилита make связана так же как и с любым другим.
Make и собственно ее манифесты(make файлы, далее как makefile) очень удобное средство, особенно для больших проектов. Она автоматизирует процесс компиляции и сборки файлов в один. На самом деле ничего сложного. Заинтересованных прошу под спойлер.Сначала о многомодульной компиляции.
Для программ вроде «хеллоу, бро» создавать отдельные файлы с кодом — лишние затраты времени. Но, когда проект растет и функций становится очень много, есть смысл вынести их в отдельный файл. Какой профит мы из этого получаем? Основные это:

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

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

#include <stdio.h>

extern void func();

int main()
{
    printf("This is main function!n");
    func();
    return 0;
}

Как видите я использовал директиву extern. Extern указывает на то, что описанная функция находится во внешнем модуле. Говоря простым языком, мы утверждает компилятору, что такая функция есть, и проверять ее наличие не стоит. Зачем на это? А затем, что эту функцию мы опишем в следующем файле. Создадим файл с именем hello.c(например), и напишем туда следующий код:

#include <stdio.h>

void func()
{
    printf("This is not main function!n");
}

Теперь соберем нашу программу следующими командами:

gcc -c main.c
gcc -c hello.c
gcc -o prog main.o hello.o

В папке должен появиться файл prog. Запускаем его. Кто забыл, делается это так:

./prog

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

printf("This is edited string!n");

Теперь скомпилируем немного по другому. Файл main.c компилировать не будем, а используем уже скомпилированный объектный файл main.o. А вот hello.c перекомпилируем. Соберем программу снова. Все это делается командами:

gcc -c hello.c
gcc -o prog main.o hello.o

Запускаем, получаем:

Как видно из скриншота, для изменения работы второй функции понадобилось лишь изменить ее файл. Теперь попробуем автоматизировать процесс, написав для этого файл скрипта программы make(Makefile).
Makefile состоит из правил. Каждое правило состоит из цели, зависимостей и команды. Выглядит это так:
цель: зависимость
    команда
Цель, зависимость и команда могут быть не только по одиночке. Например зачастую каждое правило сожержит одну цель, несколько зависимостей и несколько команд. Они просто перечисляются через пробел. Makefile для нашей программы может выглядеть так:

prog: main.o hello.o
        gcc -o prog main.o hello.o
main.o: main.c
        gcc -c main.c
hello.o: hello.c
        gcc -c hello.c

И так. Наш Makefile состоит из трех правил. Первое правило говорит: «для получения файла prog, необходимы файлы main.o hello.o, файл можно получить командой gcc -o main.o hello.o«. То есть, сначала утилита make проверит наличией файлов main.o и hello.o и если они есть — выполниться команда. А что случится если их в директории нет, например мы не скомпилировали еще main.c и hello.c? Все будет хорошо :). Потому, что кроме этого правила есть еще два. Утилита make прочитав первое правило, попытается найти описания зависимостей для файлов main.o и hello.o. Так как они написаны ниже, make скомпилирует main.c hello.c файлы и получит main.o и hello.o. Ну а теперь, когда все первого правила удовлетворены, можно выполнить команду gcc -o prog main.o hello.o. Теория запутанная, поэтому перейдем к практике. Создадим новый файл и назовем его Makefile. Важно создавать его в корневой директории программы. Например вот так:

Теперь откроем его, и напишем туда текст:

prog: main.o hello.o
        gcc -o prog main.o hello.o
main.o: main.c
        gcc -c main.c
hello.o: hello.c
        gcc -c hello.c

Сохраним и закроем. Теперь вручную удалите main.o hello.o и prog. Например такой командой:

rm *.o prog

Теперь можно запустить утилиту make. Для этого просто введите в консоль «make» и нажмите «enter». Если вы все сделали правильно, то получите:

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

А теперь снова откроем hello.c и заменим выводимую на экран строку. Например так:

printf("I am using make!n");

И запустим утилиту make. Получаем следующие:

Как видно из скришота, количество команд уменьшилось. А все потому, что мы отредактировали всего лишь hello.c. Утилита make проверила, что main.c не изменился и не стала его перекомпилировать. Проверим работоспособность программы, запустив prog. Должно выйти следующие:

Все работает.
 Примечание: Утилита make может выпонять заданные цели, например команда make hello.o заставит make разрешить зависимости только для цели hello.o!
Теперь напишем немного другой пример. Создадим программу, которая считывает два числа, и выполняет над ними действие. Функцию арифметических действий разместим в других файлах. Создадим Makefile таким образом, что бы он скомпилировал четыре программы, которые будут выполнять разные арифметические действия.
Ну что ж, приступим. Сначала main.c:

#include <stdio.h>

extern float aFunc(float a, float b); //внешняя функция

int main()
{
    float v1, v2; //входные переменные
    printf("Введите первое число:n");
    scanf("%f", &v1); //получаем первую переменную
    printf("Введите второе число:n");
    scanf("%f", &v2); //получаем вторую переменную

    //следующая функция выводит на экран результат
    printf("Результат от %f и %f равен %f!n", v1, v2, aFunc(v1, v2));
    return 0;
}

Теперь создадим четыре файла. Например slogenie.c vichitanie.c umnogenie.c delenie.c. Все делается одной командой:

touch slogenie.c vichitanie.c umnogenie.c delenie.c

Откроем например slogenie.c и напишем туда реализацию нашей функции aFunc:

#include <stdio.h>

float aFunc(float a, float b)
{
    return a + b;
}

Код очень простой, функция получает два числа и просто возвращает их сумму. Я думаю другие файлы вы сможете отредактировать сами, заменив «+» на другие знаки.
Закончили редактировать файлы реализации арифметических операций? Пришло время писать Makefile:

all: sProg vProg uProg dProg clean

sProg: main.o slogenie.o
  gcc -o sProg main.o slogenie.o
  gcc -c slogenie.c
vProg: main.o vichitanie.o
  gcc -o vProg main.o vichitanie.o
  gcc -c vichitanie.c
uProg: main.o umnogenie.o
  gcc -o uProg main.o umnogenie.o
  gcc -c umnogenie.c
dProg: main.o delenie.o
  gcc -o dProg main.o delenie.o
  gcc -c delenie.c
main.o: main.c
  gcc -c main.c

clean:
  rm *.o

Сложный Makefile. Но если разобраться все просто. Появились ранее не знакомые вам два правила, называемые «псевдо-цели». Это «all» и «clean». Правило «all» нужно для того, что бы при запуске make без параметров разрешались зависимости прописанные для «all». Так же «clean» — псевдоцель, служит для очистки каталога от файлов. В нашей случае от объектных.
Остальные правила простые. Каждая программа(sProg, vProg, uProg, dProg) зависит от main.o и еще одного объектного файла. Поэтому я сделал отдельное правило для main.o — он один на всех.
В общем запускаем make. Получаем:

Ошибок нет. Попробуем запустить каждую программу по очереди:

Все получилось как нужно.
Удачи вам.
P.S. Если что, задавайте свои ответы, а так же сообщайте мне об ошибках!

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

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