Компоновка объектов#
Почти все программы, за исключением самых простых, собираются из различных частей. Мы рассмотрим такую ситуацию более подробно.
Вот пример основной программы табулирования значений функции (исходный код в файле «»tabulate.f90»):
program tabulate
use user_functions
implicit none
real :: x, xbegin, xend
integer :: i, steps
write(*,*) 'Please enter the range (begin, end) and the number of steps:'
read(*,*) xbegin, xend, steps
do i = 0, steps
x = xbegin + i * (xend - xbegin) / steps
write(*,'(2f10.4)') x, f(x)
end do
end program tabulate
Обратите внимание на оператор use
– здесь мы определим функцию f
.
Мы хотим сделать программу основной, поэтому разместим определённый код – реализацию функции f
– отдельно от основного исходного кода. Есть несколько способов сделать это, и один из них – поместить этот код в другой файл. Мы можем дать основную программу пользователю, а он добавит необходимый исходный код для функции.
Предположим для примера, что функция реализована в файле исходного кода «functions.f90» в виде:
module user_functions
implicit none
contains
real function f( x )
real, intent(in) :: x
f = x - x**2 + sin(x)
end function f
end module user_functions
Чтобы собрать программу с этой определённой функцией нам нужно скомпилировать два файла исходного кода и объединить их с помощью компоновки в один исполняемый файл программы. Поскольку программа «tabulate» зависит от модуля «function», то сначала нам нужно скомпилировать исходный файл, содержащий наш модуль. Последовательность команд для сборки программы такова:
$ gfortran -c functions.f90
$ gfortran tabulate.f90 functions.o
На первом этапе компилируется модуль, в результате чего создаётся объектный «functions.o» и промежуточный файл модуля «user_functions.mod». Этот файл модуля содержит всю информацию, необходимую компилятору, чтобы определить, что функция f
определена в этом модуле и какой у неё интерфейс. Эта информация важна: она позволяет компилятору проверить, что вы вызываете функцию правильным образом. Может оказаться, что вы допустили ошибку и вызвали функцию с двумя аргументами вместо одного. Если компилятор ничего не знает об интерфейсе функции, то он не сможет ничего проверить.
На втором этапе компилятор вызывается таким образом, чтобы:
скомпилировать файл «tabulate.f90» (используя файл модуля);
вызвать компоновщик для объединения объектных файлов tabulate.o и functions.o в исполняемый файл программы — с именем по умолчанию «a.out» или «a.exe» (если вы хотите другое имя, используйте опцию компилятора «-o»).
Что вы обычно не видите, так это то, что компоновщик также добавляет несколько дополнительных файлов на этом шаге компоновки – библиотеки времени выполнения (run-time libraries). Эти библиотеки содержат все «стандартные» вещи — например, низкоуровневые процедуры, которые выполняют ввод и вывод на экран, математическую функцию sin
и многое другое.
Если вы хотите увидеть все детали компоновки, добавьте опцию компилятора «-v». Она даст указание компилятору, сообщать обо всех этапах сборки, которые могут быть описаны подробнее.
В конечном итоге, исполняемый файл программы содержит скомпилированный исходный код и различные вспомогательные процедуры, обеспечивающие работу программы. Она также содержит ссылки на так называемые динамические библиотеки времени выполнения (dynamic run-time libraries) – в Windows: DLL, в Linux: shared objects или shared libraries. Без этих библиотек программа не запустится.