Файл: А. Б. Шнейвайс основы программирования.pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 06.12.2023

Просмотров: 332

Скачиваний: 6

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
i
Re(c)
Im(c)
1 0.1100000E+01 0.1010000E+01 2
0.1200000E+01 0.1020000E+01 3
0.1300000E+01 0.1030000E+01 4
0.1400000E+01 0.1040000E+01 5
0.1500000E+01 0.1050000E+01
i r
1 0.1100000E+01 2
0.1010000E+01 3
0.1200000E+01 4
0.1020000E+01 5
0.1300000E+01 6
0.1030000E+01 7
0.1400000E+01 8
0.1040000E+01 9
0.1500000E+01 10 0.1050000E+01
i
Re(c)
Im(c)
1 0.1100000E+01 -0.1010000E+01 2
0.1200000E+01 -0.1020000E+01 3
0.1300000E+01 -0.1030000E+01 4
0.1400000E+01 -0.1040000E+01 5
0.1500000E+01 -0.1050000E+01
i
Re(c)
Im(c)
1 0.1100000E+01 -0.1010000E+01 2
0.1200000E+01 -0.1020000E+01 3
0.1300000E+01 -0.1030000E+01 324

11
Приложение II. Азы GNU-make (часть 2)
Освоение возможностей утилиты make на основе примера, использовав- шегося при начальном знакомстве с ней (см. пункты 4.3.1 и 4.3.2) пер- вого семестра. В 4.3.1 была рассмотрена ФОРТРАН-программа program probtask;
! Модель проблемной программы.
Файл probtask.f90
include ’probtask.hdr’
! Подключение описаний.
open(unit=ninp,file=’probtask.par’)
! Открытие файла ввода.
open(unit=nres,file=’result’)
! Открытие файла вывода.
read(ninp,100) n,a,b
! Ввод числа узлов и границ отрезка.
write(nres,110) n, a, b
! Контрольная печать введенного.
h=(b-a)/(n-1)
! Расчет шага дробления [a,b].
write(nres,1000)
! Печать заголовка первой таблицы.
do i=1, n
! Для каждого из узлов x=a+(i-1)*h
!
вычисляем текущий аргумент,
y=fun(x)/2
!
фиксируем результат fun(x),
call subfun(x,z)
!
получаем результат subfun write(nres,1001) i, x, y, z/4
!
вывод текущего результата enddo
!
close(nres)
! Закрытие файла результата.
100 format(i10/d10.3/d10.3)
! Список форматов ввода-вывода:
110 format(1x,’#
Число узлов дискретизации (n)=’,i4/&
&
1x,’# Левая граница промежутка
(a)=’,d23.16/&
&
1x,’# Правая граница промежутка (b)=’,d23.16)
1000 format(//1x,’# N’,15x,’x’,21x,’fun(x)/2’,19x,’ z/4 ’)
1001 format(1x,i3,2x,d23.16,2x,d23.16,2x,d23.16)
end
Программа выполняет n точечное табулирование с равномерным шагом по аргументу из промежутка [a,b] функций (2x-3)/2 и (2x-3)/4. Расчёт числителя оформлен двояко: функцией fun и подпрограммой subfun.
function fun(x) result(w)
! Файл fun.f90
implicit none real(8) w, x w=2d0*x-3d0
end function fun subroutine subfun(x,res)
! Файл subfun.f90
implicit none real(8) x, res res=2d0*x-3d0
end subroutine subfun
325

Здесь на простом примере напоминаются разные варианты описания ал- горитма, поскольку первое знакомство с утилитой make проходило сразу после знакомства с темой “Подпрограммы и функции” .
Настоящее приложение просто использует эту простую задачу в каче- стве полигона, на котором будут продемонстрированы дополнительные возможности утилиты make для построения проекта компиляции и ком- поновки данной программы, которая в свою очередь моделирует доста- точно широкий класс прикладных программ. Напомним существенные особенности рассматриваемого примера.
1. Функция и подпрограмма специально расположены в разных фай- лах, моделируя ситуацию компоновки многофайлового проекта.
2. В качестве расчётных формул специально выбраны тривиальные выражения именно для того, чтобы всё внимание было уделено в основном синтаксису описания функций и подпрограмм.
3. Даже по упомянутым тривиальным выражениям расчёт на ЭВМ
при значениях x очень близких к 1.5 может дать результат весьма сомнительной точности (даже на типе real(8)).
4. В главной программе имеется инструкция include ’probtask.hdr’,
которая информирует компилятор о необходимости включить в ис- ходный текст программы probtask содержимое файла с именем probtask.hdr:
!==============================================================
! Переменные главной программы
:
Файл probtask.hdr
!.........................................:....................
implicit none
! Отмена правила умолчания ФОРТРАНа.
interface function fun(x) result(w); real(8) x, w; end function fun subroutine subfun(x,w); real(8) x, w; end subroutine subfun end interface integer ninp / 5 /
! Номер файла с вводимыми параметрами задачи.
integer nres / 6 /
! --"--"--"-- c выводимымым результатом.
integer n
! Количество узлов дискретизации промежутка.
integer i
! Номер текущего узла.
real(8) a, b
! Левая и правая абсциссы его границ.
real(8) h
! Шаг его равномерной дискретизации.
real(8) x
! Текущий аргумент real(8) y
! Хранитель значения, получаемого функцией.
real(8) z
! -"--"--"--"--"--"--"--"--"- подпрограммой.
326


В данном случае таким содержимым служит раздел описания ин- терфейсов процедур и типов переменных, используемых в главной программе. Подобное (через файл-посредник) подключение описа- ний вряд ли целесообразно для короткой программы. Если перемен- ных немного, то описание их структур и типов удобнее обозревать в явном виде перед разделом выполняемых операторов, нежели ис- пользуя файл-посредник. Тем не менее подобная вставка текста мо- жет встретиться в программах, с которыми вам возможно придётся работать.
Заметим, что в probtask.hdr нет описания real(8) fun, указываю- щего тип значения, возвращаемого функцией (что было бы необхо- димо в старом ФОРТРАНе). Вместо этого в исходный текст файла включено описание интерфейса и функции fun, и подпрограммы subfun. Напомним, что наличие интерфейса позволит компилято- ру при компиляции главной программы проконтролировать соот- ветствие количества и типов фактических аргументов процедур ат- рибутам их формальных аргументов.
Директива include ранее уже встречалась нам (см. пункты 3.1.1,
3.1.6, 4.3.1). Напомним ещё раз, что директива include внедряет в место её расположения в текущем файле содержимое файла, имя которого указано ей в качестве параметра. Каким должно быть это содержимое – набор нескольких независимых программных единиц или же просто несколько ФОРТРАН-операторов – решаем мы.
При этом помним, что в случае подключения подобным способом процедур лишь создаётся иллюзия краткости исходного текста (ведь компилятор будет перекомпилировать все процедуры, входящие в файл, имя которого указано в инстркуции include). Поэтому вре- менные затраты на компиляцию программы с подключаемыми че- рез include процедурами, а так же на поиск и исправление син- тасических неточностей, могут оказаться существенно более дли- тельными чем при оптимальном подходе на основе использования координатора make.
Тем не менее уже наш пример позволит выявить некоторую неточ- ность в подготовленном нами ранее (см. пункт 4.3.2) make-файле.
327

11.1
Дальнейшие усовершенствования
1. Простые переменные утилиты make.
2. Операторы присваивания утилиты make.
3. Автоматические переменные утилиты make.
4. Уяснение выгоды от использования простых переменных.
11.1.1
Простые переменных утилиты make
Рассмотрим фрагмент make-файла (см. 4.3.2), который координирует сборку и выполнение программы probtask, приведённой в начале при- ложения 2.
probtask : probtask.o subfun.o fun.o
[
TAB
]
gfortran probtask.o subfun.o fun.o -o probtask probtask.o : probtask.f90
[
TAB
]
gfortran -c probtask.f90
subfun.o : subfun.f90
[
TAB
]
gfortran -c subfun.f90
fun.o : fun.f90
[
TAB
]
gfortran -c fun.f90
result : probtask probtask.par
[
TAB
]
./probtask
Исполнимый файл с именем probtask собирается из трех объектных probtask.o, subfun.o и fun.o, которые генерируются из трёх исходных файлов probtask.f90, subfun.f90 и fun.f90 посредством вызова компи- лятора с опцией -c.
Хотелось бы, чтобы запись make-файла выглядела наглядной, крат- кой и в меньшей мере подчёркивала бы зависимость от имени компиля- тора и от используемых им опций. Для этого команду вызова и нужные опции естественно запомнить в двух отдельных make-переменных (на- зовём их, например, COMP и OPT), содержимое которых будем извле- кать по мере надобности.
Извлечь значение из простой переменной утилиты make позволяет конструкция $(имя_переменной), а присвоить простой переменной значение (см., например, [13]) можно операторами
=
или
:=
С учётом сказанного предыдущий make-файл примет вид:
328


COMP=gfortran
OPT=-c -O0
probtask : probtask.o subfun.o fun.o
[
TAB
]
$(COMP) probtask.o subfun.o fun.o -o probtask probtask.o : probtask.f90
[
TAB
]
$(COMP) $(OPT) probtask.f90
subfun.o : subfun.f90
[
TAB
]
$(COMP) $(OPT) subfun.f90
fun.o : fun.f90
[
TAB
]
$(COMP) $(OPT) fun.f90
result : probtask probtask.par
[
TAB
]
./probtask
11.1.2
Операторы присваивания утилиты make
1. Первый
=
– традиционный способ. При использовании
=
значение переменной вычисляется в момент ее использования.
2. Второй
:=
делает переменную подобной обычным переменным язы- ков программирования ФОРТРАН, СИ или ПАСКАЛЬ, когда зна- чение переменной вычисляется в момент обработки оператора присваивания.
3. Переменная может менять свой статус в соответствии с типом опе- ратора присваивания, который применялся к ней последним. Опе- ратор
:=
кажется более простым, правда, за счет сужения возмож- ностей оператора
=
Помимо make-переменных с именами, придуманными нами, утилита make предоставляет так называемые автоматические переменные, ко- торые позволяют существенно уменьшить количество досадных опеча- ток при наборе make-файла.
329

11.1.3
Автоматические переменные утилиты make
Автоматическая переменная – переменная с особым именем, кото- рая автоматически принимает некоторое значение перед тем как вы- полняются команды достижения цели (команд может быть и несколько).
Например, правило probtask : probtask.o subfun.o fun.o
[
TAB
]
$(COMP) probtask.o subfun.o fun.o -o probtask с использованием автоматических переменных $

и $@ запишется так:
probtask : probtask.o subfun.o fun.o
[
TAB
]
$(COMP) $^
-o $@
• $

– имя автоматической переменной, содержащей список всех за- висимостей цели, в данном случае это список probtask.o subfun.o fun.o, позволяя избежать перенабора списка в команде вызова ком- поновщика.
• $@ содержит имя цели, точнее, имена всех целей, список которых находится левее
:
в заголовке рассматриваемого правила. $@ выгод- на, когда имя файла, получаемого в результате достижения цели,
должно совпадать с её именем.
COMP=gfortran
#
Возможный пример make-файла c использованием
OPT=-g -c -O0
#
автоматических переменных $^ и $@.
probtask : probtask.o subfun.o fun.o #
[
tab
]
$(COMP)
$^ -o $@
# Подчеркнем, что $^ содержит probtask.o : probtask.for
# в записи команды список ВСЕХ
[
TAB
]
$(COMP) $(OPT) $^
# зависимостей только для той цели,
subfun.o : subfun.for
# которая достигается.
[
TAB
]
$(COMP) $(OPT) $^
# Поэтому в записи правил достижения fun.o : fun.for
# целей компиляции обозначение $^
[
TAB
]
$(COMP) $(OPT) $^
# относится к файлу необходимому result : probtask probtask.par
# для достижения соответствующей
[
TAB
]
./probtask
# цели, т.е. к исходному коду.
Наряду с переменными $

и $@ может оказаться полезной и автома- тическая переменная $<, содержащая имя первой зависимости текущей цели. Заметим, что наш предыдущий make-файл обладает одним суще- ственным недостатком:
в нем никак не отражена зависимость от файла probtask.hdr.
330


Поэтому возможная модификация последнего останется незамеченной при работе утилиты make. Действительно,
1.
# Изменили время модификации файла,
$ touch probtask.hdr
# подключаемого через include,
$ make
# при наличии исполнимого кода.
make: ‘probtask’ не требует обновления.# Но make этого не заметила !!!
2.
$ rm -f probtask
# Удалили исполнимый файл, оставив объектные и освежив
$ touch probtask.hdr
# версию файла probtask.hdr. Видим, что перекомпиляции
$ make
# probtask.f90, как хотелось бы, не происходит, т.е.
#
исполнимый код собран gfortran probtask.o subfun.o fun.o -o probtask #
из старых объектных.
3. Зависимость цели probtask.o от обоих файлов и probtask.f90, и probtask.hdr объективно обоснована. Однако, при этом инструк- ция $(COMP) $∧ с необходимостью приведёт к компиляции не только исходного текста главной программы probtask.f90 (что дей- ствительно нужно), но и содержимого файла probtask.hdr (что бессмысленно, так как оно не представляет собой независимую про- граммную единицу; оно уже встроено в текст probtask.f90 посред- ством инструкции include ’probtask.hdr’). Расширение последнего hdr просто придумано программистом и не входит в список извест- ных системе по умолчанию расширений. Поэтому реакция компи- лятора может оказаться своеобразной:
$ rm -f *.o probtask; $ touch probtask.hdr
$ make gfortran -c -g -O0 probtask.for probtask.hdr gfortran: probtask.hdr: linker input file unused because linking not done gfortran probtask.o subfun.o fun.o -o probtask
Если бы имя файла, подключаемого через include было, например,
probtask.f, то реакция утилиты make предсказуема:
$ make gfortran -c -g -O0 probtask.f90 probtask.f
Error: Unexpected end of file in ’probtask.f’
make: *** [probtask.o] Ошибка 1
Дело в том, что исходный текст, подключаемый через include, не представляет собой в нашем случае единицу компиляции, син- таксически завершаемую служебным словом end. Он не должен компилироваться вне текста главной программы.
331

4. Наличие же автоматической переменной $<, как ссылки именно на первую зависимость выбранной цели, вместо $

, решает проблему:
COMP=gfortran
OPT=-c -O0
probtask : probtask.o subfun.o fun.o # Если независимая программная
[ TAB ]
$(COMP)
$^
-o $@
# ФОРТРАН-единица использует probtask.o : probtask.for probtask.hdr # инструкцию INCLUDE, то имя
[ TAB ]
$(COMP) $(OPT) $<
# файла, cодержащего её, выгодно subfun.o : subfun.f90
# указатьпервой зависимостью и
[ TAB ]
$(COMP) $(OPT) $^
# для ссылкина неё использовать fun.o : fun.f90
# автоматическую переменную $< .
[ TAB ]
$(COMP) $(OPT) $^
result : probtask probtask.par
[ TAB ]
./probtask
$ rm *.o
$ rm probtask
$ make gfortran -c -g -O0 probtask.f90
gfortran -c -g -O0 subfun.f90
gfortran -c -g -O0 fun.f90
gfortran probtask.o subfun.o fun.o -o probtask
$ touch *.hdr
$ make gfortran -c -g -O0 probtask.f90
gfortran probtask.o subfun.o fun.o -o probtask
$ make result
./probtask
$ cat result
#
Число узлов дискретизации (n)=
5
# Левая граница промежутка
(a)= 0.1499999990000000D+01
# Правая граница промежутка (b)= 0.1500000000000000D+01
# N
x fun(x)/2
z/4 1
0.1499999990000000D+01 -0.9999999939225290D-08 -0.4999999969612645D-08 2
0.1499999992500000D+01 -0.7499999954418968D-08 -0.3749999977209484D-08 3
0.1499999995000000D+01 -0.4999999969612645D-08 -0.2499999984806323D-08 4
0.1499999997500000D+01 -0.2499999984806323D-08 -0.1249999992403161D-08 5
0.1500000000000000D+01 0.0000000000000000D+00 0.0000000000000000D+00
После пропуска кода probtask получена таблица искомых функций с шагом 2.5 · 10
−9
на промежутке [1.49999999, 1.5]. Видно, что для аргумента, отличающегося от 1.5 на величину шага его дробления,
результат верен лишь в пределах восьми старших десятичных зна- чащих цифр, хотя расчёт ведется с шестнадцатью.
332


5. Напомним, что для получения результата верного в пределах 15–
16-ти значащих цифр (без перехода на многозначную арифметику)
достаточно исключить из расчётной формулы вычитание двух по- чти равных чисел в окрестности точки x=1.5. Так, если в формуле
2*x-3 сделать замену переменной x=t+1.5, используя t в качестве нового аргумента, то формула сведётся к выражению, в котором опасное вычитание уже выполнено нами аналитически:
2 ∗ x − 3 = 2(t + 1.5) − 3 = 2 ∗ t + 3 − 3 = 2 ∗ t
,
а диапазон изменения переменной t, соответствующий диапазону изменения x ∈ [a, b] окажется t ∈ [a − 1.5, b − 1.5]. При этом для задания значений границ изменения t нет нужды находить разности a-1.5 и b-1.5 посредством машинного вычитания. Их можно, вычис- лив самим один раз, просто ввести (ведь при x ∈ [1.49999999, 1.5]
нетрудно сообразить, что t ∈ [−10
−9
, 0]). Модифицируя в соответ- ствии со сказанным исходные тексты главной программы, функции,
подпрограммы и вводимых данных program probtask
! Модифицированная программа.
Файл probtask.f90
include ’probtask.hdr’
open(unit=ninp,file=’probtask.par’)
open(unit=nres,file=’result’,status=’replace’)
read(ninp,100) n,a,b
! Ввод числа узлов и границ.
write(nres,110) n, a, b
! Контрольная печать введенного.
h=(b-a)/(n-1)
! Расчет шага дробления [a,b].
write(nres,1000)
! Печать заголовка таблицы.
do i=1, n
! Для каждого из узлов:
t=a+(i-1)*h
!
новый текущий аргумент x=1.5d0+t
!
старый --"--"--"--"-- y=funt(t)/2
!
результат через funt(t),
call subfunt(t,z)
!
--"--"--"--"-- subfunt write(nres,1001) i, t, x, y, z/4 !
вывод текущего результата enddo close(nres)
100 format(i10/d10.3/d10.3)
! Список форматов ввода-вывода:
110 format(1x,’#
Число узлов дискретизации (n)=’,i4/&
&
1x,’# Левая граница промежутка
(a)=’,d23.16/&
&
1x,’# Правая граница промежутка (b)=’,d23.16)
1000 format(//1x,’# N’,5x,’t’,12x,’x’,13x,’funt(t)/2’,14x,’ z/4 ’)
1001 format(1x,i3,d10.2,f14.10,1x,d21.14,1x,d21.14)
end program probtask
333

!
Файл probtask.hdr implicit none
! Отмена правила умолчания ФОРТРАНа.
interface function funt(x) result(w); real(8) x, w; end function funt subroutine subfun(x,w); real(8) x, w; end subroutine subfun end interface integer ninp / 5 /
! Номер файла с вводимыми параметрами задачи.
integer nres / 6 /
! --"--"--"-- c выводимымым результатом.
integer n
! Количество узлов дискретизации промежутка.
integer i
! Номер текущего узла.
real(8) a, b
! Левая и правая абсциссы его границ.
real(8) h
! Шаг его равномерной дискретизации.
real(8) t, x
! Текущие аргументы real(8) y
! Хранитель значения, получаемого функцией.
real(8) z
! -"--"--"--"--"--"--"--"--"- подпрограммой.
function funt(t) result(w) ! Функция funt(t) вычисляет значение implicit none
! функции 2*t, совпадающее со значением функции real(8) w, t
! 2*x-3, если в последней сделать замену w=2d0*t
! t=x-1.5, искдючая вычитание почти равных чисел.
end function funt subroutine subfunt(t,res) ! Подпрограмма subfunt(t,res) вычисляет implicit none
! значение того же выражения, что и real(8) t, res
! и функция funt(t) (и аналогично funt).
res=2d0*t end subroutine subfunt
5<--= (n) Число узлов дискретизации.
:
Файл probtask.par
-1.0000-08<--= (a) Левая абсцисса промежутка t
:
-----------------
0.0000+00<--= (b) Правая граница промежутка t
:....................
и, используя make-файл:
COMP=gfortran
OPT=-c -O0
probtask
: probtask.o subfunt.o funt.o
# Цель subfunt.o зависит
[
TAB
]
$(COMP)
$^ -o $@
# лишь от одного файла,
probtask.o
: probtask.f90 probtask.hdr
# который не содержит
[
TAB
]
$(COMP) $(OPT) $<
# инструкцию INCLUDE.
subfunt.o : subfunt.f90
# Поэтому аргументом
[
TAB
]
$(COMP) $(OPT) $^
# команды компиляции funt.o : funt.f90
# можно использовать
[
TAB
]
$(COMP) $(OPT) $<
# любую из автоматических result : probtask probtask.par
# переменных: $^
и
$< .
[
TAB
]
./probtask
# (аналогично и цель funt.o)
334