C Урок 34. MAKE. Функции и переменные



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

Оказывается, Make может ещё много чего и на данном уроке мы в этом убедимся.

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

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

А прежде чем начнём, нам нужен будет проект, который мы сделаем из проекта прошлого урока с именем MYPROG33 и назовём его MYPROG34.

Откроем наш проект в Eclipse и откроем сразу же Makefile, так как работать на данном уроке мы будем только с ним.

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

Также в функциях могут быть аргументы.

Вызываются функции с помощью ключевого слова call

 

$(call function, arg1, arg2, )

 

А в теле функции аргументы уже используются с помощью использования переменных $1, $2 и т.д.

Давайте в нашем файле объявим функцию с простейшим телом после цели all

 

 

Телом функции является тело цели arith.o.

А в цели arith.o мы сначала удалим тело (вообще-то называется «команды», но я как-то так привык)

 

$(CC) -Iinc -O0 -g3 -Wall -c src/arith.c

$(CC) -Iinc -masm=intel -g3 -Wall -c src/arith.c -S

 

И вместо этого вызовем нашу функцию

 

 

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

Аналогичные вещи проделаем также и с целями utils.o и student.o.

Сначала добавим функции после функции build-obj-arith

 

 

А затем также удалим тела данных целей и вместо этого вызовем наши функции

 

 

 

Проект по-прежнему отлично собирается.

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

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

Такие функции вызываются по-другому, без ключевого слова call, также перед первым аргументом запятая не ставится

 

$(function arg1, arg2, )

 

Немного усложним задачу, так как использование функции без аргументов не совсем оправдано, так как наши 3 функции никакой, в принципе, экономии в коде не дали.

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

 

 

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

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

 

 

Проверим, что проект наш по прежнему отлично собирается.

Отдохнём немного от функций и займёмся переменными.

Оказывается, что помимо обычных переменных, make умеет работать с автоматическими переменными.

Таких переменных не так много, но их набор вполне достаточен. Их всего 4

 

  1. $@ — имя цели
  2. $< — имя первой зависимости
  3. $? — имена всех зависимостей, которые новее чем цель
  4. $^ — имена всех зависимостей цели

 

 

Перепишем теперь тело нашей функции с использованием автоматической переменной

 

 

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

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

 

 

Проверяем работу и движемся дальше.

Давайте имя конечного исполняемого файла вынесем в отдельную переменную

 

 

Заметим. что в основной цели all у нас в команде перечисляются все зависимости

 

all: main.o arith.o utils.o student.o

$(CC) -o myprog34.exe main.o arith.o utils.o student.o

 

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

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

 

 

Проверяем, идём далее.

Давайте применим нашу функцию ещё и для цели создания файла main.o, в принципе нам все заголовочные файлы в ней отслеживать необязательно. Это будет нужно для дальнейшей автоматизации проекта. Цель примет теперь вот такой вид

 

 

Идём дальше.

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

Для примера давайте исследуем состав каталога src. Для этого объявим сначала соответствующую переменную

 

 

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

 

 

И в главной цели выведем в консоль значение данной переменной

 

 

Посмотрим, что у нас вывелось

 

src/utils.c src/main.c src/student.c src/arith.c

 

Это и есть все файлы нужного нам каталога, причём вывелись они вместе с именем самого каталога.

Отлично!

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

$(dir имена…)

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

 

 

Выведем в теле основной цели также значение этой переменной в консоль

 

 

В результате мы получим вот что

 

src/ src/ src/ src/

 

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

 

 

Оказывается, есть ещё и функция, обратная по значению — notdir, которая, наоборот, отсекает имя каталога из строки, а оставляет только имя файла с расширением.

Исправим нашу строку с присвоением

 

files := $(notdir $(src_files))

 

Также изменим имя переменной в выводе в консоль

 

@echo $(files)

 

А вот и результат

 

utils.c main.c student.c arith.c

 

Неплохо.

Также есть интересная функция, которая оставит от файла только его базовое имя (без расширения). Эта функция носит имя basename. Применяется аналогичным образом

 

$(basename имена…)

 

Давайте с помощью данной функции уберём расширение от имени файла, добавив в нашу строку с присвоением использование такой функции

 

files := $(basename $(notdir $(src_files)))

 

Значение, полученное с помощью функции notdir, мы обработали здесь ещё функцией basename.

Вот результат

 

utils main student arith

 

Ещё одна интересная функция — это функция addsuffix. С помощью неё мы можем наоборот к имени файла добавить справа требуемое расширение

 

$(addsuffix суффикс,имена…)

 

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

 

obj_files := $(addsuffix .o, $(basename $(notdir $(src_files))))

 

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

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

Также исправим имя переменной в выводе в консоль

 

@echo $(obj_files)

 

Проверим как работают наши функции

 

utils.o main.o student.o arith.o

 

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

Теперь мы можем данный список спокойно использовать в нашей главной цели в качестве зависимостей

 

all: $(obj_files)

 

Далее самое интересное.

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

Удалим эти четыре цели и добавим теперь только одну

 

 

Красиво! Но дело не в красоте. Мы теперь не видим нигде в сценарии имён наших файлов. Это даёт нам возможность добавлять модули в наши каталоги inc и src, не как не исправляя Makefile, то есть он их подхватит теперь автоматически. Проверим ещё раз, как собирается наш проект и попробуем добавить теперь ещё один модуль.

Перед тем, как мы его добавим, вот эти строки мы можем смело удалить

 

@echo $(src_files)

@echo $(obj_files)

 

Давайте функции print_str, print_chars и print_uint32_arr объединим в отдельный модуль, чтобы убрать их из модуля main.

Создадим модуль с именем print_user, для этого сначала создадим в каталоге inc заголовочный файл с именем print_user.h следующего содержания

 

 

Также, соответственно, в каталоге src создадим файл print_user.с следующего содержания

 

 

Перенесём в данный файл из файла main.c вышеназванные функции вместе с телами, удалив их из main.c.

Создадим на данные функции в заголовочном файле прототипы и в файле main.c подключим наш новый модуль

 

 

В файле main.c также удалим прототипы данных функций

 

void print_str(const char *c_str);

void print_chars(const char *c_str);

void print_uint32_arr(const unsigned int *p_uint, unsigned int len);

 

В функции main() объявим локальный символьный массив

 

 

В самом конце функции main() перед возвратом протестируем наши функции, которые мы перенесли в отдельный модуль

 

 

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

Мы видим, что наш новый модуль в процессе сборки также полноправно участвует

 

gcc -Iinc -O0 -g3 -Wall -c src/print_user.c

gcc -Iinc -masm=intel -g3 -Wall -c src/print_user.c -S

 

Запустим наш проект и увидим, что все 3 функции прекрасно работают

 

 

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

Всем спасибо за внимание!

 

 

Предыдущий урок Программирование на C Следующий урок

 

Исходный код

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

C Name

4 комментария на “C Урок 34. MAKE. Функции и переменные
  1. sdj_turbo:

    Уважаемый АВТОР! Вы как то говорили, что может быть будете делать уроки по плюсам C++.
    Или уже передумали? если же нет, то когда планируете запустить проект по C++?

  2. yuri:

    Просьба, если есть возможность сделать урок по конечным автоматам с AVR и на Си.
    Спасибо.

  3. Спасибо. Ничего подобного я не видел в инете. Вы научили меня любить Си. Жду с нетерпением продолжения.

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

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

*