Компоновка объектов#

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

Вот пример основной программы табулирования значений функции (исходный код в файле «»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. Без этих библиотек программа не запустится.