ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 06.12.2023
Просмотров: 327
Скачиваний: 6
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
получим окончательный результат:
#
Число узлов дискретизации (n)=
5
# Левая граница промежутка
(a)=-0.1000000000000000D-07
# Правая граница промежутка (b)= 0.0000000000000000D+00
# N
t x
funt(t)/2
z/4 1 -0.10D-07 1.4999999900 -0.10000000000000D-07 -0.50000000000000D-08 2 -0.75D-08 1.4999999925 -0.75000000000000D-08 -0.37500000000000D-08 3 -0.50D-08 1.4999999950 -0.50000000000000D-08 -0.25000000000000D-08 4 -0.25D-08 1.4999999975 -0.25000000000000D-08 -0.12500000000000D-08 5
0.00D+00 1.5000000000 0.00000000000000D+00 0.00000000000000D+00 11.1.4
Уяснение выгоды простых make-переменных
Несмотря на упрощение make-файла при использовании переменных $
∧
,
$< и $@ имена исходных и объектных модулей рассредоточены по всему тексту make-файла, что неудобно при изменении или добавлении имён.
Удобно, чтобы подобная модификация не касалась содержательной ча- сти make-файла, а была сосредоточена в каком-то одном его фрагменте.
Желаемое достигается помещением имени (или части имени, если нужно) каждого файла с исходными модулями в соответствующую про- стую make-переменную. Тогда целевая часть make-файла формально выразится через имена простых make-переменных. Тем самым все изме- нения коснутся правых частей лишь нескольких операторов присваива- ния, сгруппированных в начальном фрагменте make-файла. Например,
наш предыдущий проект собирался из четырёх файлов:
probtask.for, probtask.hdr, funt.for и subfunt.for
Поместим:
1. Слово probtask — в make-переменную, например, с именем MAIN,
что напомнит о статусе главной программы и имени главной цели.
2. Сочетание .hdr — в make-переменную INCL.
3. Имена процедур funt и subfunt — в переменные F1 и F2.
В make-переменных MAIN, F1, F2 выгодно хранить имена файлов без расширений (для генерации нужных имён расширения можно добавлять в виде суффикса: например, $(MAIN), $(MAIN).f90, $(MAIN).hdr,
335
#
Число узлов дискретизации (n)=
5
# Левая граница промежутка
(a)=-0.1000000000000000D-07
# Правая граница промежутка (b)= 0.0000000000000000D+00
# N
t x
funt(t)/2
z/4 1 -0.10D-07 1.4999999900 -0.10000000000000D-07 -0.50000000000000D-08 2 -0.75D-08 1.4999999925 -0.75000000000000D-08 -0.37500000000000D-08 3 -0.50D-08 1.4999999950 -0.50000000000000D-08 -0.25000000000000D-08 4 -0.25D-08 1.4999999975 -0.25000000000000D-08 -0.12500000000000D-08 5
0.00D+00 1.5000000000 0.00000000000000D+00 0.00000000000000D+00 11.1.4
Уяснение выгоды простых make-переменных
Несмотря на упрощение make-файла при использовании переменных $
∧
,
$< и $@ имена исходных и объектных модулей рассредоточены по всему тексту make-файла, что неудобно при изменении или добавлении имён.
Удобно, чтобы подобная модификация не касалась содержательной ча- сти make-файла, а была сосредоточена в каком-то одном его фрагменте.
Желаемое достигается помещением имени (или части имени, если нужно) каждого файла с исходными модулями в соответствующую про- стую make-переменную. Тогда целевая часть make-файла формально выразится через имена простых make-переменных. Тем самым все изме- нения коснутся правых частей лишь нескольких операторов присваива- ния, сгруппированных в начальном фрагменте make-файла. Например,
наш предыдущий проект собирался из четырёх файлов:
probtask.for, probtask.hdr, funt.for и subfunt.for
Поместим:
1. Слово probtask — в make-переменную, например, с именем MAIN,
что напомнит о статусе главной программы и имени главной цели.
2. Сочетание .hdr — в make-переменную INCL.
3. Имена процедур funt и subfunt — в переменные F1 и F2.
В make-переменных MAIN, F1, F2 выгодно хранить имена файлов без расширений (для генерации нужных имён расширения можно добавлять в виде суффикса: например, $(MAIN), $(MAIN).f90, $(MAIN).hdr,
335
$(MAIN).o, $(MAIN).par, и т.д.). Таким образом предыдущий make- файл преобразуется к виду:
COMP:=gfortran
OPT:=-c -Wall -O0
MAIN:=probtask
INCL:=.hdr
F1:=funt
F2:=subfunt
$(MAIN)
: $(MAIN).o $(f1).o $(f2).o
[
TAB
]
$(COMP) $^ -o $@
$(MAIN).o
: $(MAIN).for $(MAIN)$(INCL)
[
TAB
]
$(COMP) $(OPT) $< -o $@
$(f1).o
: $(f1).f90
[
TAB
]
$(COMP) $(OPT) $^ -o $@
$(f2).o
: $(f2).f90
[
TAB
]
$(COMP) $(OPT) $< -o $@
RESULT
: $(MAIN) $(MAIN).par
[
TAB
]
./$<
Полученный make-файл практически пригоден для сборки любого про- екта, компонуемого из трёх объектных модулей и одной include-вставки.
Для перестройки данного make-файла на задачу, описываемую исход- никами с другими именами (например, prog.for, prog.hd, prog.par, s1.for,
s2.for) достаточно соответственно изменить в нём правые части операто- ров присваивания в 3-й, 4-й, 5-й и 6-й строках (main:=prog, incl:=.hd,
f1:=s1, f2:=s2).
При наличии большого количества объектных файлов целесообразно завести простую make-переменную, например, с именем OBJ, которая будет содержать весь их список. С нею запись правила получения испол- нимого кода окажется гораздо более компактной и наглядной:
OBJ=$(MAIN).o $(f1).o\# Значок "\" (обратный слэш) --- признак
$(f2).o#
продолжения текущего оператора в следующей строке.
$(MAIN)
: $(OBJ)
[
TAB
]
$(COMP) $^ -o $@
«Комбинирование» правил с одинаковой целью.
В предыдущем make- файле запись зависимостей правила получения объектного кода главной программы можно оформить чуть иначе. Например, так:
$(MAIN).o : $(MAIN).hdr
# т.е. ОДНУ ЦЕЛЬ можно указать
$(MAIN).o : $(MAIN).f90
# несколько раз, определяя в качестве
[
TAB
]
$(COMP) $(OPT) $<
# нужных те из зависимостей, которые
# по каким-то причинам неудобны при
336
или так
# однократном описании цели: утилита
# make допускает"комбинирование" правил
$(MAIN).o : $(MAIN).f90
# с одинаковой целью. Оба приведенные
[
TAB
]
$(COMP) $(OPT) $<
# варианта эквивалентны строке:
$(MAIN).o : $(MAIN).hdr
#
$(MAIN).o : $(MAIN).f90 $(MAIN).hdr
#
[ TAB ]
: $(COMP) $(OPT) $<
Конечно, здесь подобное комбинирование кажется на первый взгляд вы- чурным. Оно выгодно при автоматической генерации имён целей,
когда некоторые из них нужно уточнить дополнительными зависимостя- ми (пока этой возможностью утилиты make мы ещё не пользовались,
зато теперь узнали про неё).
Легко понять, что, если из сорока целей только две требуют include- подключения, то странно указывать необходимость такого подключения в списке зависимостей остальных тридцати восьми.
Вот тут-то нас и выручает «комбинирование»: автоматическая гене- рация всех объектных целей использует лишь те зависимости, которые можно обозначить общим для них способом, а упомянутые выше две це- ли, зависящие от include-подключений, уточняем двумя, соответствую- щими только этим целям, дополнительными зависимостями, аналогично приведенным выше фрагментам make-файла.
11.2
Автоматическая генерация списка объектных файлов
Напомним, что
1) автоматические переменные позволили указать неявно имена объ- ектных и исходных файлов;
2) простые переменные позволили практически исключить из заголов- ков правил оригинальные имена исходных модулей;
3) однако для получения каждого объектного модуля в make-файле имеется отдельное правило, так что чем больше исходных файлов,
тем больше объём и самого make-файла, что вряд ли удобно.
Ранее отмечалось, что утилита make имеет средство, позволяющее ни разу явно не указать ни одного конкретного имени объектного фай- ла и, тем не менее, полностью скомпоновать загрузочный код, то есть выполнить главную цель. Укажем плохое и приемлемое решения.
337
# однократном описании цели: утилита
# make допускает"комбинирование" правил
$(MAIN).o : $(MAIN).f90
# с одинаковой целью. Оба приведенные
[
TAB
]
$(COMP) $(OPT) $<
# варианта эквивалентны строке:
$(MAIN).o : $(MAIN).hdr
#
$(MAIN).o : $(MAIN).f90 $(MAIN).hdr
#
[ TAB ]
: $(COMP) $(OPT) $<
Конечно, здесь подобное комбинирование кажется на первый взгляд вы- чурным. Оно выгодно при автоматической генерации имён целей,
когда некоторые из них нужно уточнить дополнительными зависимостя- ми (пока этой возможностью утилиты make мы ещё не пользовались,
зато теперь узнали про неё).
Легко понять, что, если из сорока целей только две требуют include- подключения, то странно указывать необходимость такого подключения в списке зависимостей остальных тридцати восьми.
Вот тут-то нас и выручает «комбинирование»: автоматическая гене- рация всех объектных целей использует лишь те зависимости, которые можно обозначить общим для них способом, а упомянутые выше две це- ли, зависящие от include-подключений, уточняем двумя, соответствую- щими только этим целям, дополнительными зависимостями, аналогично приведенным выше фрагментам make-файла.
11.2
Автоматическая генерация списка объектных файлов
Напомним, что
1) автоматические переменные позволили указать неявно имена объ- ектных и исходных файлов;
2) простые переменные позволили практически исключить из заголов- ков правил оригинальные имена исходных модулей;
3) однако для получения каждого объектного модуля в make-файле имеется отдельное правило, так что чем больше исходных файлов,
тем больше объём и самого make-файла, что вряд ли удобно.
Ранее отмечалось, что утилита make имеет средство, позволяющее ни разу явно не указать ни одного конкретного имени объектного фай- ла и, тем не менее, полностью скомпоновать загрузочный код, то есть выполнить главную цель. Укажем плохое и приемлемое решения.
337
11.2.1
Плохое решение
Очевидная замена в предыдущем make-файле содержимого переменной
OBJ с $(MAIN).o $(F1).o $(F2).o на *.o не выдерживает критики.
Действительно, пусть в текущей директории имеются исходные файлы probtask.f90, probtask.hdr, funt.f90, subfunt.f90
и объектные probtask.o, funt.o, subfunt.o. Используя их можно по- строить исполнимый код упомянутым выше порочным способом:
COMP:=gfortran
OPT:=-c -Wall -O0
MAIN:=probtask
INCL:=.hdr
F1:=funt
F2:=subfunt
OBJ:=*.o# <--=
Порочный способ усовершенствования !!!
$(MAIN)
: $(OBJ)
[
TAB
]
$(COMP) $^ -o $@
$(MAIN).o
: $(MAIN).hdr
$(MAIN).o
: $(MAIN).f90
[
TAB
]
$(COMP) $(OPT) $<
$(F1).o
: $(F1).f90
[
TAB
]
$(COMP) $(OPT) $^
$(F2).o
: $(F2).f90
[
TAB
]
$(COMP) $(OPT) $<
RESULT : $(MAIN) $(MAIN).par
[
TAB
]
./$<
Протестируем работу этого make-файла:
$ touch *.f90
# Изменили время модификации файлов *.f90
$ make
# и gfortran -c -g -O0 funt.f90
# убедились, что make-файл вроде бы gfortran -c -g -O0 probtask.f90
# работоспособен gfortran -c -g -O0 subfunt.f90
gfortran funt.o probtask.o subfunt.o -o probtask
$ touch *.hdr
# Аналогичная картина и при
$ make
# модификации файлов *.hdr gfortran -c -g -O0 probtask.for gfortran funt.o probtask.o subfunt.o -o probtask
$
# Однако удаление объектных
$ rm *.o
# файлов приводит к сообщению:
$ make make: *** Нет правила для сборки цели ‘*.o’, требуемой для ‘probtask’.
Останов.
338
Поскольку в текущей директории не оказалось ни одного объектного файла, то утилита и информирует нас об этом, причём весьма своеоб- разно: полагая, что имя цели должно быть *.o. Помним, что шаблон «*»
в имени файла означает любую строку символов (лишь бы она не начи- налась с точки), в частности, и имена объектных кодов, получающиеся после компиляции соответствующих исходных. Поэтому при наличии в текущей директории объектных кодов шаблон *.o обеспечит их сборку.
Однако при их отсутствии оболочка не находит в директории объектов для расширения шаблона, о чём и сообщает упомянутым образом.
Заметим, что данная ситуация – отсутствие всех объектников – наибо- лее благоприятна для нас: увидев сообщение утилиты, мы сразу начнём проверять их наличие, то есть поведём поиск причины отсутствия объ- ектных кодов по правильному пути. Если же, например, файл subfunt.o оказался стертым случайно, а остальные объектные присутствуют, то за- пуск тестируемого make-файла даст
$ make
# Породить фразу gfortran funt.o probtask.o -o probtaskн
# " undefined
| неопределено probtask.o(.text+0x21f): In function ‘MAIN__’: # reference
| обращение
: undefined reference to ‘subfunt_’
# to ’subfunt_’" | к subfunt collect2: ld returned 1 exit status
# могут две причины:
make: *** [probtask] Ошибка 1 1) либо случайно стёрт объектный файл подпрограммы subfunt;
2) либо имя подпрограммы, по которому к ней обращаемся, не совпада- ет с именем подпрограммы, которая описана в файле subfunt.f90.
Поиск причины в худшем случае придётся вести дважды, теряя время.
Вывод:
при компоновке исполнимого кода НЕ ИМЕНУЕМ объектные коды посредством шаблона *.o, который для командной оболочки служит лишь приказом получить список имён объектных файлов, реаль- но имеющихся в директории, но не активирует механизм их получения.
339
11.2.2
Приемлемое решение
Заметим, что последнее плохое «усовершенствование» упрощает запись сборки исполнимого файла лишь при наличии всех нужных объектных,
но никак не уменьшает ни количества правил, ни конкретных имён ис- ходных файлов в списках зависимостей.
Нам же надо построить список объектных целей, который находится автоматически по списку известных исходных файлов. Таким образом,
здесь можно поставить следующие две задачи.
1. Получить список исходных файлов, являющихся единицами ком- пиляции (файлы с главной программой, процедурами, возможно с
ФОРТРАН-модулями), которые необходимыми для решения зада- чи, т.е. файлы, например, c расширением f90. Сделать это можно,
используя функцию wildcard, встроенную в утилиту make.
2. Преобразовать найденный список имён исходных файлов в соответ- ствующий список имён объектных (т.е. заменить в каждом из най- денных имён расширение f90 на расширение o). Эту замену можно провести посредством функции patsubst, также встроенную в ути- литу make.
Функция wildcard.
По переданному ей шаблону (или шаблонам) нахо- дит список имён cоответствующих файлов. В качестве шаблона исполь- зуем известное файловой системе сочетание *.f90 (или *.f, или *.f95),
так что запуск make-файла
PATTERN:=*.f90
SOURCE :=$(wildcard $(PATTERN))
NAME :
[
TAB
] @echo $(SOURCE)
выведет на экран список файлов текущей директории, соответствующий шаблону *.f90,
$ make funt.for probtask.for subfunt.for
Напомним, что значок @ перед командой echo гасит вывод текста ко- манды, т.е. при отсутствии значка @ получили бы
340
$ make echo funt.for probtask.for subfunt.for funt.for probtask.for subfunt.for
Если переменная PATTERN содержит строку *.for *.f95 *.f90 (с тре- мя шаблонами), а функция subfunt размещена в файле subfunt.f90,
главная программа — в файле probtask.f95, а функция funt — в файле funt.for, хотя подобный разнобой в расширениях вряд ли можно при- знать удачной альтернативой, то, тем не менее, активация make-файла
PATTERN:=*.f *.f95 *.f90
SOURCE :=$(wildcard $(wild))
NAME
:
[
TAB ]
@echo $(SOURCE)
получит в переменной SOURCE список всех файлов текущей директо- рии с указанными шаблонами:
make subfunt.f90 funt.f probtask.f95
Функция patsubst
(аббревиатура от pattern substitution — замена одного шаблона в списке имён другим) в соответствии с переданными ей
1) исходным шаблоном (например, %.f90);
2) новым желательным вариантом шаблона (например, %.o);
3) символьной строкой, состоящей из слов, отделяемых друг от друга не менее чем одним пробелом (например, списком имён исходных
ФОРТРАН-файлов с расширением f90)
заменяет в каждом cлове исходный шаблон (если он подходит к слову)
на желаемый выриант шаблона. Здесь символ % – особый шаблон, озна- чающий любое количество произвольных символов. Например, если в текущей директории имеются файлы с именами funt.f90, probtask.f90,
subfunt.f90 и probtask.hdr, то при активации make-файла
PATTERN:=*.f90
SOURCE :=$(wildcard $(PATTERN))
OBJ:=$(patsubst %.f90, %.o, $(SOURCE))
NAMES
:
[
TAB
]
@echo $(SOURCE)
NAMEO :
[
TAB
]
@echo $(OBJ)
341
переменные SOURCE и OBJ получат следующие значения:
$ make NAMES
funt.f90 probtask.f90 subfunt.f90
$
$ make NAMEO
funt.o probtask.o subfunt.o
Таким образом, посредством функции wildcard можно составить список всех исходных файлов с единицами компиляции, хранящимися в текущей директории, а посредством функции patsubst получить соответствую- щий им список имён всех необходимых объектных файлов. Так что, если переменная MAIN нацелена на хранение имени исполнимого файла, а переменная OBJ – хранит список всех нужных объектных, то фрагмент получения исполнимого кода попрежнему запишется двумя строками
$(MAIN) : $(OBJ)
$(COMP) $^ -o $@
Другими словами, функции wildcard и patsubst избавляют програм- миста от необходимости вручную явно набирать конкретные имена фай- лов, входящих в проект, что несомненно очень удобно.
Единственное, что пока вызывает нарекание – разбухание make-файла при увеличении количества объектных целей, ведь на каждую цель тре- буется в простейшем случае две строки: строка зависимостей и строка команды, посредством которой цель достигается. Упомянутый недоста- ток просто устраняется посредством использования шаблонов %.f90 и
%.o (см. выше вызов функции patsubst) в так называемых так называ- емых шаблонных правилах.
342
$ make NAMES
funt.f90 probtask.f90 subfunt.f90
$
$ make NAMEO
funt.o probtask.o subfunt.o
Таким образом, посредством функции wildcard можно составить список всех исходных файлов с единицами компиляции, хранящимися в текущей директории, а посредством функции patsubst получить соответствую- щий им список имён всех необходимых объектных файлов. Так что, если переменная MAIN нацелена на хранение имени исполнимого файла, а переменная OBJ – хранит список всех нужных объектных, то фрагмент получения исполнимого кода попрежнему запишется двумя строками
$(MAIN) : $(OBJ)
$(COMP) $^ -o $@
Другими словами, функции wildcard и patsubst избавляют програм- миста от необходимости вручную явно набирать конкретные имена фай- лов, входящих в проект, что несомненно очень удобно.
Единственное, что пока вызывает нарекание – разбухание make-файла при увеличении количества объектных целей, ведь на каждую цель тре- буется в простейшем случае две строки: строка зависимостей и строка команды, посредством которой цель достигается. Упомянутый недоста- ток просто устраняется посредством использования шаблонов %.f90 и
%.o (см. выше вызов функции patsubst) в так называемых так называ- емых шаблонных правилах.
342
Шаблонные правила.
IMPLICIT rules или PATTERN rules — средство,
позволяющее автоматизировать процесс получения всех объектных пра- вил, упростить make-файл и сделать его более универсальным [11, 12,
13]. Оказывается, нет нужды выписывать каждое объектное правило.
Достаточно привлечь уже знакомые нам шаблоны %.f90 и %.o для за- писи одного объектного правила, которое, способно (в целом) заменить все требующиеся.
Укажем в строке зависимостей вместо конкретного имени объектной цели её шаблонное обозначение %.o и вместо конкретного имени исход- ного файла его шаблонное обозначение %.f90. В частности, Правило
%.o : %.f90
[
TAB
]
gfortran -c $^
проинформирует утилиту о том, что каждый файл с шаблоном %.o за- висит от соответствующего файла с шаблоном %.f90. При этом сим- вол процента в имени зависимости правила заменяется текстом, соот- ветствующим символу процента в имени цели. Таким образом, соответ- ствующие объектные и исполнимый файлы можно получить, активируя make-файл:
COMP:=gfortran
OPT:=-c -Wall -O0
PATTERN:=*.f90
SOURCE :=$(wildcard $(PATTERN))
OBJ
:=$(patsubst %.f90, %.o, $(SOURCE))
MAIN
:=probtask
$(MAIN) : $(OBJ)
[
TAB
]
$(COMP) $^ -o $@
%.o : %.f90
[
TAB
]
$(COMP) $(OPT) $<
$(MAIN).o : $(MAIN).hdr
RESULT : $(MAIN) $(MAIN).par
[
TAB
]
./$^
сколько бы в текущей директории не находилось файлов с расширением f90. При этом передача компилятору имен файлов, от которых зависит исполнение команд достижения цели, происходит через автоматическую переменную $
∧
, не требуя явного указания имен файлов.
343
11.2.3
Тестирование приемлемого make-файла
1. Пусть в текущей директории нет объектных файлов – только нуж- ные исходные:
$ rm *.o
$ make gfortran -c -Wall -O0 funt.f90
# перекомпилированы все исходники и gfortran -c -Wall -O0 probtask.f90
# собран исполнимый код gfortran -c -Wall -O0 subfunt.f90
gfortran funt.o probtask.o subfunt.o -o probtask
2. Предположим, что случайно стерли funt.o:
$ rm funt.o
$ make
# перекомпилирован только funt.for gfortran -c -Wall -O0 funt.f90
#
для получения нового funt.o gfortran funt.o probtask.o subfunt.o -o probtask
3. Пусть внесено изменение в файл, подключаемый к главной програм- ме посредством инструкции include:
$ touch *.hdr
# главная программа перекомпилирована
$ make
# отработали комбинирование правил и gfortran -c -wall -O0 probtask.f90 #
переменная $<
gfortran funt.o probtask.o subfunt.o -o probtask
4. Если думаем, что что-то изменили, а на самом деле нет, то
$ make
# make напомнит об этом,
make: ‘probtask’ не требует обновления. # не делая лишней работы.
5. Допустим, что изменили содержимое файла probtask.par (хотим пропустить программу с иными исходными данными).,
$ touch *.par
# то make RESULT выполнит это, так как
$ make RESULT
# цель RESULT зависит от файла с исходными данными,
./probtask
# о чём "говорит" автоматическая переменная $^.
6. Если программа с имеющимися исходными данными уже была про- пущена, но мы в этом сомневаемся и (на всякий случай) инициируем повторное выполнение программы:
344