Компиляторы, флаги и сборка проекта

Чтобы код жил долго, его нужно собирать предсказуемо: компиляторы, флаги, многофайловые проекты и инструменты сборки.

Компиляция — преобразование исходного текста на Fortran в объектный и затем исполняемый код заранее, до запуска; в отличие от интерпретации, она происходит один раз и даёт быстрый бинарник.

Программа на Fortran бесполезна, пока не превратилась в исполняемый файл. В предыдущих уроках мы вызывали gfortran одной командой, но реальные проекты состоят из десятков файлов, требуют флагов оптимизации и контроля ошибок. Этот урок — практическое руководство по сборке: какие компиляторы выбрать, какие флаги включать в учебном и боевом режиме, как собирать многофайловый проект и какие инструменты автоматизируют рутину. Без этого фундамента любой содержательный код останется текстом, который негде запустить.

Выбор компилятора

Fortran — стандартизованный язык, но воплощают стандарт разные компиляторы. Знать их сильные стороны полезно: учебный код можно собрать чем угодно, а в реальном проекте выбор влияет на скорость и диагностику.

КомпиляторКто делаетОсобенности
gfortranпроект GNU (GCC)Бесплатный, кросс-платформенный, отличная диагностика, наш основной
ifxIntelБесплатный, на основе LLVM, сильная оптимизация под x86
nvfortranNVIDIAДля GPU-ускорения (OpenACC, CUDA Fortran)
LFortran / Flangсообщество / LLVMНовые компиляторы, активно развиваются

Для всего курса достаточно gfortran: он ставится из пакетного менеджера (apt install gfortran в Debian/Ubuntu, через Homebrew на macOS, через MSYS2 или пакет на Windows) и даёт понятные сообщения об ошибках. Когда вы дорастёте до выжимания производительности на конкретном железе, имеет смысл сравнить с ifx.

Флаги компиляции, которые надо знать

Голый вызов gfortran file.f90 работает, но в серьёзной работе всегда добавляют флаги. Они делятся на две группы: для разработки (максимум проверок) и для релиза (максимум скорости).

# Учебная/отладочная сборка: ловим всё
gfortran -O0 -g -Wall -Wextra -fcheck=all -fbacktrace prog.f90 -o prog

# Боевая сборка: оптимизация
gfortran -O2 -march=native prog.f90 -o prog

Разберём ключевые флаги. -O0-O3 задают уровень оптимизации (от никакой до агрессивной); -g добавляет отладочную информацию для отладчика. -Wall и -Wextra включают предупреждения — относитесь к ним как к ошибкам. -fcheck=all вставляет проверки времени выполнения, главная из которых — выход за границы массива; без неё такая ошибка молча портит память, с ней — даёт внятное сообщение. -fbacktrace печатает стек вызовов при аварии. В релизе всё это убирают ради скорости, а -march=native разрешает использовать инструкции именно вашего процессора.

Многофайловый проект

Настоящие программы разбиты на файлы: модуль с типами, модуль с процедурами, главная программа. Сборка проходит в два этапа — компиляция каждого файла в объектный (.o) и линковка объектных файлов в исполняемый. Рассмотрим минимальный пример из двух файлов. Сначала модуль:

! файл geometry.f90
module geometry
  implicit none
  real, parameter :: pi = 3.14159265358979_8
contains
  pure function circle_area(r) result(a)
    real, intent(in) :: r
    real :: a
    a = pi * r**2
  end function circle_area
end module geometry

Затем главная программа, которая использует модуль через use:

! файл main.f90
program main
  use geometry, only: circle_area
  implicit none
  print *, "Площадь:", circle_area(2.0)
end program main

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

gfortran -c geometry.f90      # -c: только компиляция, без линковки -> geometry.o + geometry.mod
gfortran -c main.f90          # видит geometry.mod
gfortran geometry.o main.o -o app   # линковка
./app

Вывод:

Площадь:   12.5663710

Инструменты сборки

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

  • make — классический инструмент: вы описываете правила в Makefile, и он сам решает, что пересобрать. Универсален, но требует ручного описания зависимостей между модулями.
  • CMake — генератор сборочных систем, понимает Fortran-модули, удобен для кросс-платформенных проектов.
  • fpm (Fortran Package Manager) — современный официальный менеджер пакетов и сборщик. Создаёт проект командой fpm new, собирает fpm build, запускает fpm run, тянет зависимости из реестра. Для новых проектов это самый простой путь.

Минимальный fpm-проект задаётся файлом fpm.toml, и вся возня с порядком компиляции модулей ложится на инструмент.

name = "myproject"
version = "0.1.0"

[build]
auto-executables = true

Стадии компиляции и линковки подробнее

Путь от исходника до программы стоит представлять как конвейер из нескольких стадий, потому что ошибки на каждой стадии выглядят по-разному. Сначала препроцессинг (если используется, например, для #include в файлах .F90 с заглавной F) — текстовая подстановка. Затем разбор и семантический анализ: компилятор строит дерево программы, проверяет типы, опирается на .mod-файлы для внешних имён. Здесь ловятся «синтаксические» и «типовые» ошибки — необъявленная переменная, неверный аргумент. Далее идёт оптимизация внутреннего представления и генерация объектного кода — получается .o-файл с машинными инструкциями, но с ещё не разрешёнными ссылками на внешние процедуры. Наконец, линковщик сшивает все .o-файлы и библиотеки, разрешая символы: вызов circle_area в одном файле связывается с её телом в другом. Ошибки линковки («undefined reference») принципиально иные, чем ошибки компиляции: они означают, что символ нигде не найден или объявлен, но не определён, — частый признак того, что вы забыли добавить нужный .o-файл или библиотеку в команду.

Понимание этого конвейера экономит часы отладки. Если gfortran ругается на «Cannot open module file» — это стадия разбора, не нашёлся .mod; решение в порядке сборки. Если жалуется линковщик — недостаёт объектного файла. А если код собрался, но падает с «Segmentation fault» — это уже рантайм, и тут спасает пересборка с -fcheck=all -g -fbacktrace, превращающая немой крах в осмысленное сообщение с номером строки.

Внешние библиотеки: BLAS и LAPACK

Реальный численный код почти никогда не пишут «с нуля» — он опирается на проверенные библиотеки, и умение их подключать так же важно, как знание синтаксиса. Две легендарные библиотеки — BLAS (базовые операции линейной алгебры: умножение матриц, скалярные произведения) и LAPACK (решение систем уравнений, собственные значения, разложения). Они написаны и вылизаны десятилетиями, существуют в оптимизированных под конкретное железо вариантах (OpenBLAS, Intel MKL) и стоят в основе численного мира, включая, как ни странно, Python: NumPy и SciPy вызывают именно их. Подключение к фортрановской программе сводится к указанию библиотеки линковщику флагом -l, например -llapack -lblas, и при необходимости пути к ней через -L. Это превращает сборку в нечто вроде «скомпилировать мой код и связать его с этими готовыми числодробилками», что и есть типичный рабочий процесс инженера: своя логика плюс опора на индустриальные библиотеки, а не велосипеды.

Как работает под капотом

Почему модуль нужно компилировать первым? Когда gfortran обрабатывает module geometry, он порождает два артефакта: объектный файл geometry.o с машинным кодом процедур и текстово-бинарный файл geometry.mod с описанием интерфейсов — какие в модуле есть процедуры, какие у них типы аргументов, какие константы. Когда позже компилируется main.f90 со строкой use geometry, компилятор читает geometry.mod и проверяет, что вы вызываете circle_area правильно — с одним аргументом типа real. Если .mod-файла ещё нет, компиляция main.f90 провалится. Этот механизм даёт Fortran строгую проверку типов между файлами — то, чего в старом C достигали ненадёжными заголовками.

Линковщик на финальном шаге сшивает объектные файлы: он находит, что вызов circle_area в main.o ссылается на тело функции в geometry.o, и связывает их по символьным именам. Поэтому в команде линковки перечисляют все .o-файлы.

Стоит понимать и экономический смысл раздельной компиляции. В проекте из сотен файлов изменение одного исходника не должно тянуть за собой пересборку всех остальных — это были бы часы ожидания. Раздельная компиляция позволяет пересобрать только затронутый .o и заново слинковать; именно эту логику зависимостей и автоматизируют make, CMake и fpm, сравнивая даты изменения файлов. Но у Fortran есть коварство, которого нет в C: если вы поменяли интерфейс модуля (добавили аргумент в процедуру), пересобрать нужно не только сам модуль, но и всех его пользователей, ведь их .mod-зависимость устарела. Инструменты сборки, понимающие Fortran-модули, отслеживают это автоматически; самодельный Makefile без описания модульных зависимостей здесь подведёт — отсюда популярность fpm и CMake, которые знают про .mod из коробки.

Частые ошибки

  • Неправильный порядок компиляции. Компиляция main.f90 раньше модуля даёт ошибку «Cannot open module file geometry.mod». Решение — собрать модуль первым или поручить порядок инструменту (make/fpm).
  • Отсутствие -fcheck=all при отладке. Без него выход за границу массива не диагностируется и приводит к случайным сбоям; в учебной сборке этот флаг почти обязателен.
  • Перенос предупреждений «на потом». Предупреждения -Wall часто указывают на реальные баги (необъявленная переменная, несоответствие типов аргументов). Игнорировать их опасно.
  • Релизные флаги при отладке. С -O2 отладчик показывает «оптимизированные» строки не по порядку; для отладки нужен -O0 -g.
  • Несовместимые .mod-файлы. Файлы .mod привязаны к версии компилятора; при смене gfortran их нужно пересоздать.

Итоги

  • Fortran компилируется заранее; основной бесплатный компилятор курса — gfortran (альтернатива — Intel ifx).
  • Отладочная сборка: -O0 -g -Wall -Wextra -fcheck=all -fbacktrace; релизная: -O2 -march=native.
  • Многофайловый проект собирают в два этапа: -c компилирует каждый файл в .o, затем их линкуют.
  • Модуль порождает .mod с интерфейсом и обязан компилироваться раньше тех, кто его использует.
  • Для автоматизации применяют make, CMake или современный fpm (Fortran Package Manager).
  • Флаг -fcheck=all ловит выход за границы массива — включайте его при разработке.
Проверьте себя
1. Что делает флаг -fcheck=all в gfortran?
AУскоряет программу
BВставляет проверки времени выполнения, включая выход за границы массива
CОтключает предупреждения
DЛинкует все объектные файлы
2. Почему модуль нужно компилировать раньше использующей его программы?
AТак быстрее
BМодуль создаёт .mod-файл с описанием интерфейса, который читает использующий код
CЭтого не требуется
DИначе не сработает оптимизация