Compilers and Compiler Options
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Fortran 2023 (ISO/IEC 1539-1:2023, J3/23-007r1) and was checked on 2026-10-19. Every
example was compiled with GNU Fortran 16.2 (gfortran, -std=f2023 -Wall -Wextra -pedantic unless the example is about
a flag) and run, and the examples whose flags exist in both compilers were also built and run with LLVM Flang 23
(flang, release 23.1.4 as installed from apt.llvm.org in the test container, an aarch64 Debian image). The output shown
is real output. The deliberate-error examples show the real diagnostics and say which compiler printed them. The Intel
ifx and NAG compilers, and the HPE Cray, NVIDIA and AMD compilers, are not installed; what this page says about them
is named as such and is not run. Where a table row comes from a vendor manual that could not be read as text on the
check date (the Intel Fortran Compiler Developer Guide pages for 2026.1 load as an empty shell for a text extractor), the
row says so instead of quoting an option from memory.
A compiler option is by definition outside the language: the standard (J3/23-007r1, clause 4.2, "Conformance") says
what a processor shall be able to detect and report, which includes obsolescent forms, forms that violate the numbered
syntax rules and constraints, nonstandard intrinsic procedures and nonstandard intrinsic modules, and says that
the processor may accept additional forms. How a user asks the processor to report those things, to check the program at
run time, to optimize it, or to place its module files is entirely processor-dependent. That is why this page is a
per-compiler reference: the same programs, built with the right options, are the cheapest bug-finding tool the language
has, and the options differ between GNU Fortran, LLVM Flang, ifx and NAG. The running example is heatflow; its fpm
project and the Jacobi kernel appear in several listings. This page covers one compiler invocation. The order in which
many files are compiled, and the tools that automate it, are in
Building with Make, CMake and Meson and
Fortran Package Manager (fpm), module files in
Modules and Submodules, the compiler landscape and the
per-compiler F2023 support table in
History, Standards and Compilers, floating-point
exceptions in Floating-Point and IEEE Arithmetic,
and debugger, valgrind and profiler workflows in
Testing, Debugging and Profiling.
Which manual to read
The compiler version decides which options exist, and documentation sites are not always versioned the way the compiler is:
-
The GNU Fortran manual at
gcc.gnu.org/onlinedocs/gfortran/(no version in the path) is the development branch. Compared with the installed 16.2 compiler it documents-fcoarray=sharedand-Wundefined-vars, and 16.2 rejects both (gfortran: error: Unrecognized option: 'shared', with the note that the valid arguments to-fcoarray=arelib none single). Use the versioned manual,gcc.gnu.org/onlinedocs/gcc-16.2.0/gfortran/, for the release you run. The manual has no Fortran 2023 status page (theFortran-2023-status.htmladdress does not exist), so the support of individual F2023 features comes from the table in History, Standards and Compilers and the compiler’s own diagnostics. -
LLVM Flang documents its driver at
flang.llvm.org/docs/, the standards support inFortranStandardsSupport.htmland the extensions inExtensions.html. The executable isflang(it wasflang-newbefore LLVM 20; the Debian package used here installsflang-23, which the examples invoke). -
Intel’s compiler is
ifx. The classicifortwas discontinued in oneAPI 2025.0, so older options lists and answers that sayifortdescribe a compiler that no longer ships. -
The authoritative list of a compiler’s options is always the compiler itself:
gfortran --help=fortran,gfortran --help=warnings,gfortran --help=optimizersandflang --helpprinted the tables used below. Every flag shown on this page was run against the installed compiler. A flag the compiler does not know is an error (flang: error: unknown argument: '-fcheck=all'), except where a driver deliberately warns and ignores it, as Flang does for-Wextra.
What a compiler invocation does
gfortran and flang are drivers. One command line is expanded into up to four programs, each with its own
options, and the options you give are routed to the stage that understands them:
.f90 .F90 .f .for"] --> PP["preprocess
cpp or built-in (.F90, -cpp)"] PP --> FE["compile
parse, check, optimize"] FE --> MOD["module files
.mod .smod (-J, -module-dir)"] FE --> AS["assemble
.o"] AS --> LD["link
libgfortran or libflang_rt, libm, -l libs"] LD --> EXE["executable"]
The flags that stop the pipeline early are the same for both compilers:
| Flag | Stops after | Produces |
|---|---|---|
|
preprocessing |
source text on standard output ( |
|
parsing and semantic checks |
no object file; a fast check |
|
compilation |
assembly ( |
|
assembly |
an object file ( |
none of these |
linking |
an executable ( |
|
nothing is run |
the exact sub-commands the driver would execute, which is the quickest way to see which libraries and system paths are added |
|
as usual, verbosely |
the same sub-commands, printed while they run |
gfortran -dumpversion and gfortran -dumpmachine print 16.2.0 and aarch64-linux-gnu in the test
container, and flang --version prints the Debian packaging version string. The file extension chooses the source
form and whether the preprocessor runs: .f90, .f95, .f03 and .f08 are free form without
preprocessing, .F90 and friends are free form with preprocessing, .f, .for, .ftn are fixed form
(see Program Structure and Source Form), and
-ffree-form/-ffixed-form override the guess in both compilers. The details of each stage are below; the build-system
view (compiling_source, linking_pieces, runtime_libraries) is on the Fortran-lang build pages listed in the
References.
Choosing the language level: -std and -pedantic
The first decision for a portable code base is which language it promises. GNU Fortran takes the answer from
-std=:
| Option | Meaning in GNU Fortran 16.2 (gfortran --help=fortran) |
|---|---|
|
conform to that edition; features from a later edition and GNU extensions are errors or warnings |
|
Fortran 2008 including the TS 29113 features (the C-interoperability descriptors) |
|
"experimental Fortran 202y features", the working name of the revision after 2023 |
|
the default: the standard language plus the GNU extensions |
|
accept extensions to support legacy code |
|
warn about the use of extensions to the selected standard, and make the GNU extensions an error under a |
|
all intrinsic procedures are available whatever |
The unflagged default is -std=gnu, so a program that compiles silently with plain gfortran file.f90 can still be
nonconforming. Flang’s -std= is far narrower: it accepts only f2018 and says so for every other value
(error: Only -std=f2018 is allowed currently., printed for -std=f2023, -std=f2008, -std=f2003 and
-std=legacy in LLVM 23.1.4). Its -pedantic option turns on portability warnings. Flang accepts the F2023 features it
implements without any switch, so -std=f2018 is a diagnostics setting and not a feature gate. The next program
uses sind, one of the degree-argument trigonometric functions that Fortran 2023 added; gfortran prints the
edition in its own diagnostic.
program std_levels
use, intrinsic :: iso_fortran_env, only: real64
implicit none
real(real64) :: angle, s
character(len=:), allocatable :: label
angle = 30.0_real64
s = sind(angle) ! degree-argument trigonometry, Fortran 2023
label = 'sind(30) = '
print '(a, f0.3)', label, s
end program std_levels
gfortran -std=f2023 std_levels.f90 -o std_levels && ./std_levels
gfortran -std=f2018 std_levels.f90 -o std_levels
flang std_levels.f90 -o std_levels && ./std_levels
flang -std=f2023 std_levels.f90 -o std_levels
sind(30) = .500
Warning: The intrinsic 'sind' at (1) is not included in the selected standard but new in Fortran 2023 and 'sind' will be treated as if declared EXTERNAL. Use an appropriate '-std=' option or define '-fall-intrinsics' to allow this intrinsic. [-Wintrinsics-std]
Error: Function 'sind' at (1) has no IMPLICIT type
sind(30) = .500
error: Only -std=f2018 is allowed currently.
The first line is gfortran with -std=f2023, the next two are gfortran with -std=f2018 (the warning and then the error
it causes, because with the intrinsic switched off sind is an undefined external function and implicit none rejects
it), the fourth line is Flang’s run and the last is the error from -std=f2023 in Flang. The four lines of the
diagnostic have been shortened to the two messages, since gfortran also prints the source line and a caret for each.
Source: J3/23-007r1, 16.9 Standard generic intrinsic procedures (SIND) and 4.2 Conformance.
The matching options elsewhere, as far as the vendor manuals name them: Intel ifx selects the checked standard with
-stand (the issue baseline for this section names -stand f23), NAG has one switch per edition, and the Flang
documentation notes that all the compilers' conformance options "issue warnings if non-standard features are used"
(Flang OptionComparison page, which lists -Mstandard for classic Flang, -std=level for GNU and -stand level for
Intel). The ifx page for -stand could not be read as text on 2026-10-19, so the argument list is not repeated here.
A conforming program should compile under -std=f2023 on gfortran with no warnings from -pedantic. That is the
single most valuable portability test, and the one used for every example in this guide. It does not prove
portability: the standard leaves much processor-dependent, and the other compilers support different subsets of
Fortran 2023 (see the support table in
History, Standards and Compilers).
Warnings
GNU Fortran
-Wall in GNU Fortran is not "every warning": it enables a documented list, and -Wextra adds more. The versioned
manual for 16.2 gives:
| Group | Warnings it enables |
|---|---|
|
|
|
|
|
extensions to the chosen standard |
Warnings that none of these groups switch on, and that are worth enabling in a strict build (all exist in 16.2):
| Option | What it finds |
|---|---|
|
implicit conversions between types and kinds that |
|
a call to a procedure with no explicit interface; it does not check consistency across program units |
|
a call to a procedure that is neither explicitly declared nor |
|
a |
|
intrinsic assignment that may insert (re)allocation code for the allocatable left-hand side; |
|
array temporaries created at a call or in an expression, so that they can be removed from hot loops |
|
possible aliasing of dummy arguments (in |
|
character expressions that are truncated on assignment |
|
constant underflow, a user procedure named like an intrinsic, a pointer that may outlive its target |
|
turns every warning into an error; each |
The first example gathers the classic mistakes the -Wall -Wextra set catches at compile time. It builds on a
heatflow-style computation, but each defect is deliberate.
program warnings_demo
use, intrinsic :: iso_fortran_env, only: real32, real64
implicit none
integer :: i, unused_count
integer :: n
real(real32) :: single
real(real64) :: double, tol
real(real64) :: v(4)
n = 7
double = 0.1_real64
single = double ! narrowing assignment
tol = 1.0e-3_real64
if (double == 0.1_real64) print '(a)', 'exact comparison'
print '(a, i0)', 'ratio = ', 1 / 2 + n
do i = 1, 4
v(i) = real(i, real64) * tol
end do
print '(a, f0.3)', 'sum = ', sum(v)
print '(a, f0.7)', 'single = ', single
end program warnings_demo
gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all -g warnings_demo.f90 -o warnings_demo
./warnings_demo
warnings_demo.f90:15:32:
15 | print '(a, i0)', 'ratio = ', 1 / 2 + n
| 1
Warning: Integer division truncated to constant '0' at (1) [-Winteger-division]
warnings_demo.f90:12:11:
12 | single = double ! narrowing assignment
| 1
Warning: Possible change of value in conversion from REAL(8) to REAL(4) at (1) [-Wconversion]
warnings_demo.f90:14:6:
14 | if (double == 0.1_real64) print '(a)', 'exact comparison'
| 1
Warning: Equality comparison for REAL(8) at (1) [-Wcompare-reals]
warnings_demo.f90:4:17:
4 | integer :: i, unused_count
| 1~~~~~~~~~~~
Warning: Unused variable 'unused_count' declared at (1) [-Wunused-variable]
exact comparison
ratio = 7
sum = .010
single = .1000000
Each warning names the option that produced it in brackets, which is the option to pass to -Wno-... or -Werror=....
single printed as .1000000 and not .1000000015 because the conversion to real32 happened as the warning said.
Flang builds the same program silently and prints the same four output lines; given -Wall it replies
flang: warning: argument unused during compilation: '-Wall', and given -Wextra
the warning option '-Wextra' is not supported. Source: GNU Fortran 16.2 manual: Options to request or suppress errors and warnings.
The second example turns on the stylistic warnings that are not part of -Wall, to show what each reports. The module
scale_lib is used without an only list, a subroutine ignores a dummy argument, an array is grown by reassigning it,
and an external subroutine is called without an interface.
module scale_lib
use, intrinsic :: iso_fortran_env, only: real64
implicit none
contains
function grown(a) result(b)
real(real64), intent(in) :: a(:)
real(real64), allocatable :: b(:)
b = [a, a(size(a))] ! automatic (re)allocation of the left-hand side
end function grown
subroutine scale_all(a, factor, unused_flag)
real(real64), intent(inout) :: a(:)
real(real64), intent(in) :: factor
logical, intent(in) :: unused_flag ! never read
a = a * factor
end subroutine scale_all
end module scale_lib
program extra_warnings
use scale_lib
implicit none
real(real64) :: x(3)
real(real64), allocatable :: y(:)
integer :: n
external :: legacy_report
x = [1.0_real64, 2.0_real64, 3.0_real64]
call scale_all(x, 2.0_real64, .true.)
y = grown(x)
n = size(y)
call legacy_report(n)
end program extra_warnings
subroutine legacy_report(n)
implicit none
integer, intent(in) :: n
print '(a, i0)', 'n = ', n
end subroutine legacy_report
gfortran -std=f2023 -Wall -Wextra -pedantic -Wimplicit-interface -Wimplicit-procedure -Wuse-without-only \
-Wrealloc-lhs -Wconversion-extra extra_warnings.f90 -o extra_warnings
./extra_warnings
extra_warnings.f90:19:7:
19 | use scale_lib
| 1
Warning: USE statement at (1) has no ONLY qualifier [-Wuse-without-only]
extra_warnings.f90:29:23:
29 | call legacy_report(n)
| 1
Warning: Procedure 'legacy_report' called with an implicit interface at (1) [-Wimplicit-interface]
extra_warnings.f90:10:35:
10 | subroutine scale_all(a, factor, unused_flag)
| 1~~~~~~~~~~
Warning: Unused dummy argument 'unused_flag' at (1) [-Wunused-dummy-argument]
extra_warnings.f90:8:5:
8 | b = [a, a(size(a))] ! automatic (re)allocation of the left-hand side
| 1~
Warning: Code for reallocating the allocatable array at (1) will be added [-Wrealloc-lhs]
extra_warnings.f90:27:3:
27 | y = grown(x)
| 1~
Warning: Code for reallocating the allocatable array at (1) will be added [-Wrealloc-lhs]
n = 4
The program also builds and runs with Flang (n = 4). The -Wrealloc-lhs warnings are information, not defects:
automatic reallocation on assignment is standard behavior (see
Allocatable Arrays and Dynamic Memory)
and the warning matters only in a hot loop, where -fno-realloc-lhs (Flang spells it -fno-realloc-lhs too) removes
the check at the price of a program that is wrong if the shapes ever differ. Source: GNU Fortran 16.2 manual: Options to request or suppress errors and warnings.
Flang, ifx and NAG
Flang reports most checks as errors or warnings by default and has no equivalent of -Wall/-Wextra: it ignores
those two options with a warning. What it has is -pedantic (portability warnings for extensions), -Werror,
-W<warning> and -Wno-... for the warnings it names individually (for example -Wno-out-of-bounds-subscripts, from
its extensions page), -w to silence everything, -fimplicit-none (below) and -Rpass/-Rpass-missed for
optimization remarks. The diagnostics are worded differently from gfortran’s and are not tagged with the flag that
caused them, except for the driver warnings.
| Purpose | GNU Fortran 16.2 (run) | LLVM Flang 23 (run) | Intel ifx and NAG (not run) |
|---|---|---|---|
Standard level |
|
|
ifx |
Most warnings |
|
defaults, no |
ifx |
Warnings as errors |
|
|
vendor manuals |
Runtime checks |
|
none |
ifx |
Traceback on error |
default; |
none, no |
ifx |
Floating-point traps |
|
none |
vendor manuals |
Initialize locals |
|
none ( |
vendor manuals |
The last three ifx and NAG rows only point to the manuals because their option pages were not readable as text on the check date. Treat any ifx or NAG option on this page as "named by the vendor, not exercised here".
Runtime checks and finding bugs
Static warnings cannot see an index computed at run time. The cheapest way to find out-of-range subscripts, use of undefined values and floating-point exceptions is to run the real program with checks switched on, in a build used by tests and by developers, never in a performance run.
-fcheck=…
-fcheck= takes a comma-separated list of keywords; each can be negated with the prefix no-. The versioned manual
for GNU Fortran 16.2 lists:
| Keyword | Check |
|---|---|
|
every check below |
|
a run-time warning once per call site that creates an array temporary for an actual argument |
|
invalid arguments to the bit-manipulation intrinsics |
|
array subscripts against declared bounds, and indices of assumed-shape and deferred-shape arrays; also string lengths in some array constructors |
|
a loop variable modified inside its |
|
memory allocation failures (an explicit |
|
references through disassociated pointers and unallocated allocatables |
|
a call that recurses into a procedure that is not |
An unknown keyword is a fatal error from the compiler proper (f951: Fatal Error: Argument to '-fcheck' is not valid: foo).
The old spellings -fbounds-check and -fcheck-array-temporaries still exist and are marked deprecated in the manual.
The running example is a loop that writes one element past the end of an array:
program bounds_demo
use, intrinsic :: iso_fortran_env, only: real64
implicit none
real(real64) :: u(5)
integer :: i, n
u = 1.0_real64
n = 5
do i = 1, n + 1
u(i) = u(i) * 2.0_real64
end do
print '(a, f0.1)', 'sum = ', sum(u)
end program bounds_demo
gfortran -std=f2023 -g -fcheck=bounds bounds_demo.f90 -o bounds_demo && ./bounds_demo
gfortran -O2 bounds_demo.f90 -o bounds_demo && ./bounds_demo
flang bounds_demo.f90 -o bounds_demo && ./bounds_demo
At line 9 of file bounds_demo.f90
Fortran runtime error: Index '6' of dimension 1 of array 'u' above upper bound of 5
Error termination. Backtrace:
The backtrace that follows lists numbered frames with addresses; with -g the frame for the program shows
in bounds_demo and the source line, and without -g it shows ???. The process exits with status 2. Without
-fcheck=bounds the same source behaves differently:
bounds_demo.f90:9:28:
9 | u(i) = u(i) * 2.0_real64
| ^
Warning: iteration 5 invokes undefined behavior [-Waggressive-loop-optimizations]
bounds_demo.f90:8:8:
8 | do i = 1, n + 1
| ^
note: within this loop
sum = 10.0
That is the -O2 gfortran build: the optimizer proves that the loop is out of range, warns, and the program
runs on and prints a wrong sum. The unoptimized build and the Flang build run silently (Flang printed sum = 10.0
as well). Writing one past the end clobbered memory and nothing complained, which is exactly what the check exists to
prevent. LLVM Flang 23 has no runtime bounds checking: -fcheck=all, -fbounds-check, -fbacktrace,
-ffpe-trap=..., -finit-real=snan and -fsanitize=address are all rejected with unknown argument. Use gfortran, ifx
or NAG as the checking build, and valgrind or the other tools described in
Testing, Debugging and Profiling.
Source: GNU Fortran 16.2 manual: Options for code generation conventions.
Backtraces
On a fatal runtime error or signal the GNU Fortran runtime prints a backtrace by default: the 16.2 manual documents
-fno-backtrace as the way to turn it off, and the runs above printed backtraces without -fbacktrace (that option is
still accepted and is what most examples on the web quote). A frame without a source line needs -g. Use -g in every development and
CI build; it adds debug information without asking for different optimization.
Uninitialized variables and signaling NaNs
An uninitialized local is the second most common numerical bug. The compiler can only warn when the optimizer sees
the use; the program below reads total before assigning it.
program uninit_demo
use, intrinsic :: iso_fortran_env, only: real64
implicit none
real(real64) :: total
real(real64) :: x(3)
x = [1.0_real64, 2.0_real64, 3.0_real64]
total = total + sum(x) ! total was never initialised
print '(a, f0.1)', 'total = ', total
end program uninit_demo
gfortran -std=f2023 -Wall -Wextra -pedantic -O2 uninit_demo.f90 -o uninit_demo && ./uninit_demo
gfortran -std=f2023 -g -finit-real=snan -ffpe-trap=invalid uninit_demo.f90 -o uninit_demo && ./uninit_demo
flang uninit_demo.f90 -o uninit_demo && ./uninit_demo
uninit_demo.f90:7:63:
7 | total = total + sum(x) ! total was never initialised
| ^
Warning: 'total' is used uninitialized [-Wuninitialized]
uninit_demo.f90:4:19:
4 | real(real64) :: total
| ^~~~~
note: 'total' declared here
total = 6.0
The warning appears only with optimization (the same file at -O0 gave a may be used uninitialized warning from
-Wmaybe-uninitialized), and the printed value is whatever the stack held, which here happened to be zero: the
program printed total = 6.0 and looks right. A different build of the same source printed a huge negative number,
because the value is indeterminate. With -finit-real=snan every uninitialized local real is set to a signaling NaN,
and with -ffpe-trap=invalid the first arithmetic use stops the program at the line:
Program received signal SIGFPE: Floating-point exception - erroneous arithmetic operation.
Backtrace for this error:
followed by frames, which with -g end in uninit_demo at line 7. Without the trap, the same -finit-real=snan
build prints total = NaN, which is the other way to notice. Flang printed total = 6.0. The manual states
that -finit-real takes zero, inf, -inf, nan or snan, that the other initialization options are
-finit-integer=n, -finit-logical=true|false, -finit-character=n and -finit-local-zero, that -finit-derived
extends them to components, and that pointers, allocatables and equivalence variables are not initialized by any of
them; enabling a -finit-* option also silences -Wuninitialized for the affected locals. -finit-local-zero is a
trap for portability: it hides the bug on one compiler and the program fails on the next. Use snan, not zero.
Floating-point traps
By default IEEE exceptions do not stop the program: a division by zero yields infinity and sets a flag.
program fpe_demo
use, intrinsic :: iso_fortran_env, only: real64
implicit none
real(real64) :: dx, a, b
dx = 0.0_real64
a = 1.0_real64
b = a / dx
print '(a, es10.3)', 'b = ', b
end program fpe_demo
gfortran -std=f2023 fpe_demo.f90 -o fpe_demo && ./fpe_demo
gfortran -std=f2023 -g -ffpe-trap=invalid,zero,overflow fpe_demo.f90 -o fpe_demo && ./fpe_demo
flang fpe_demo.f90 -o fpe_demo && ./fpe_demo
b = Infinity
with the trap the program instead prints (the number of frames and the addresses vary):
Program received signal SIGFPE: Floating-point exception - erroneous arithmetic operation.
Backtrace for this error:
and the frame for fpe_demo carries the source line 7 with -g. The message text is the generic SIGFPE one and does not
say "division by zero"; the backtrace is what locates the operation. -ffpe-trap= takes none or a list of invalid,
zero, overflow, underflow, inexact and denormal; the manual says the first three "usually indicate serious
errors", that repeated options are combined, and that no traps are enabled by default. -ffpe-summary=list prints the
flag status to the error unit on stop/error stop; by default every exception except inexact is reported.
Flang has no trap option. The portable alternative, which works in every compiler that implements the
ieee_exceptions intrinsic module, is call ieee_set_halting_mode(ieee_divide_by_zero, .true.) at the start of the
program (see Floating-Point and IEEE Arithmetic).
Array temporaries
Passing a strided section to an explicit-shape dummy argument forces the compiler to copy the data in and out. It is correct but can silently dominate a loop’s time. The warning is static, the check is dynamic:
module scale_mod
use, intrinsic :: iso_fortran_env, only: real64
implicit none
contains
subroutine scale_explicit(a, n)
integer, intent(in) :: n
real(real64), intent(inout) :: a(n) ! explicit-shape: needs contiguous data
a = a * 2.0_real64
end subroutine scale_explicit
end module scale_mod
program temps_demo
use, intrinsic :: iso_fortran_env, only: real64
use scale_mod, only: scale_explicit
implicit none
real(real64) :: grid(4, 4)
grid = 1.0_real64
call scale_explicit(grid(2, :), 4) ! row section is strided: copy-in/copy-out
print '(a, f0.1)', 'sum = ', sum(grid)
end program temps_demo
gfortran -std=f2023 -Wall -Wextra -pedantic -Warray-temporaries -fcheck=array-temps temps_demo.f90 -o temps_demo
./temps_demo
temps_demo.f90:18:22:
18 | call scale_explicit(grid(2, :), 4) ! row section is strided: copy-in/copy-out
| 1
Warning: Creating array temporary at (1) for argument 'a' [-Warray-temporaries]
At line 18 of file temps_demo.f90
Fortran runtime warning: An array temporary was created for argument 'a' of procedure 'scale_explicit'
sum = 20.0
The fix is an assumed-shape dummy (a(:)), which accepts the section without a copy; see
Array Arguments: Assumed Shape, Size and Rank.
Note that -fcheck=all includes array-temps, which is why the strict build in every other example prints this
warning at run time when it happens.
Implicit typing
implicit none in source is the right control, but a flag can enforce it on code that lacks it, such as a legacy
file you are auditing: gfortran has -fimplicit-none, and Flang has -fimplicit-none too (type) and
-fimplicit-none-ext (also external procedures, the equivalent of implicit none (external) from clause 8.7 for every
scope).
The program below is deliberately written the old way, as legacy code you may meet: it has no implicit none and relies
on implicit typing, which is what the flag catches. New code always declares implicit none; see
Legacy Features and Modernization.
program implicit_demo
count = 3
print *, count
end program implicit_demo
gfortran -fimplicit-none implicit_demo.f90 -o implicit_demo
flang -fimplicit-none implicit_demo.f90 -o implicit_demo
implicit_demo.f90:2:3:
2 | count = 3
| 1~~~~
Error: Symbol 'count' at (1) has no IMPLICIT type
error: Semantic errors in implicit_demo.f90
implicit_demo.f90:2:3: error: No explicit type declared for 'count'
count = 3
^^^^^
implicit_demo.f90:3:12: error: No explicit type declared for 'count'
print *, count
^^^^^
The first block is gfortran’s, the second is Flang’s. For the external-procedure part, a program with implicit none
that calls mystery(2) with no interface or external is rejected by flang -fimplicit-none-ext ('mystery' is an
external procedure without the EXTERNAL attribute in a scope with IMPLICIT NONE(EXTERNAL)), while gfortran only
warns with -Wimplicit-procedure unless the source says implicit none (type, external).
Source: GNU Fortran 16.2 manual: Options controlling Fortran dialect.
Optimization
Optimization flags are the least portable part of any build, and the one where a wrong guess costs the most.
| Flag | gfortran 16.2 | Flang 23 |
|---|---|---|
|
no optimization, fastest compile, best for debuggers (default) |
same |
|
progressively more; |
same |
|
more aggressive optimization |
same |
|
optimize for size, and for the debugging experience (GCC level options) |
see |
|
|
deprecated: the help text says to use |
|
compile for the build host’s CPU (x86; on this aarch64 container |
|
|
link-time optimization across files |
|
|
|
|
|
see below |
see below |
|
OpenMP, OpenACC, and OpenMP implies |
|
The effect on the heatflow kernel, a 1000 by 1000 Jacobi sweep run 400 times, built as two files so that link-time optimization has something to do:
module kernel_mod
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: jacobi_sweep
contains
subroutine jacobi_sweep(u, unew, residual)
real(real64), intent(in) :: u(:, :)
real(real64), intent(out) :: unew(:, :)
real(real64), intent(out) :: residual
integer :: i, j
residual = 0.0_real64
unew = u
do j = 2, size(u, 2) - 1
do i = 2, size(u, 1) - 1
unew(i, j) = 0.25_real64 * (u(i-1, j) + u(i+1, j) + u(i, j-1) + u(i, j+1))
residual = max(residual, abs(unew(i, j) - u(i, j)))
end do
end do
end subroutine jacobi_sweep
end module kernel_mod
program bench_main
use, intrinsic :: iso_fortran_env, only: real64, int64
use kernel_mod, only: jacobi_sweep
implicit none
integer, parameter :: n = 1000, steps = 200
real(real64), allocatable :: u(:, :), unew(:, :)
real(real64) :: residual
integer(int64) :: t0, t1, rate
integer :: k
allocate (u(n, n), unew(n, n))
u = 0.0_real64
u(1, :) = 100.0_real64
call system_clock(t0, rate)
do k = 1, steps
call jacobi_sweep(u, unew, residual)
call jacobi_sweep(unew, u, residual)
end do
call system_clock(t1)
print '(a, es10.3)', 'residual = ', residual
print '(a, f0.2, a)', 'elapsed = ', real(t1 - t0, real64) / real(rate, real64), ' s'
end program bench_main
gfortran -O3 -c kernel_mod.f90 && gfortran -O3 -c bench_main.f90
gfortran -O3 kernel_mod.o bench_main.o -o bench && ./bench
Every build printed residual = 6.050E-02, so the answers agree to the printed digits. The times, from one run on one
aarch64 container (so only the ratios mean anything; never copy timings from a table like this one into a decision):
| gfortran flags | Seconds | Flang flags | Seconds |
|---|---|---|---|
|
3.60 |
|
2.69 |
|
0.59 |
|
0.59 |
|
0.33 |
|
0.48 |
|
0.35 |
|
0.51 |
|
0.63 |
|
link failed, not run |
|
0.31 |
||
|
0.30 |
The lessons are the usual ones, and the numbers support them: the jump from -O0 to -O2 is an order of magnitude,
the extra flags beyond -O2 are a small or nil gain for this kernel, and the runtime checks cost little here because the
loop accesses are simple (in a loop with an indirect access they can cost far more). Measure your own code.
-march=native makes an executable that may not run on another CPU of the same family; do not use it for artifacts you
ship.
-ffast-math
-ffast-math licenses the compiler to treat floating-point arithmetic as if it were real arithmetic: reassociate sums,
use reciprocals, and assume no NaN and no infinity. The cost is that code that relies on IEEE semantics silently breaks.
The program below asks whether the square root of a negative number is a NaN:
program fastmath_demo
use, intrinsic :: iso_fortran_env, only: real64
use, intrinsic :: ieee_arithmetic, only: ieee_is_nan
implicit none
real(real64) :: x
x = -1.0_real64
x = sqrt(x)
print '(a, l1)', 'ieee_is_nan(sqrt(-1)) = ', ieee_is_nan(x)
print '(a, l1)', 'neither <= nor > holds = ', .not. (x > 0.0_real64 .or. x <= 0.0_real64)
end program fastmath_demo
gfortran -O2 fastmath_demo.f90 -o fastmath_demo && ./fastmath_demo
gfortran -O2 -ffast-math fastmath_demo.f90 -o fastmath_demo && ./fastmath_demo
flang -O2 fastmath_demo.f90 -o fastmath_demo && ./fastmath_demo
flang -O2 -ffast-math fastmath_demo.f90 -o fastmath_demo && ./fastmath_demo
ieee_is_nan(sqrt(-1)) = T
neither <= nor > holds = T
ieee_is_nan(sqrt(-1)) = F
neither <= nor > holds = F
ieee_is_nan(sqrt(-1)) = T
neither <= nor > holds = T
ieee_is_nan(sqrt(-1)) = F
neither <= nor > holds = T
The groups are gfortran without the flag, gfortran with it, Flang without and Flang with it. With -ffast-math both
compilers answer that sqrt(-1) is not a NaN, and gfortran also folds the ordered-comparison test to false, so
every NaN check in the program is unreliable. The two compilers broke different expressions, which is exactly why you
cannot predict the damage. -ffast-math is not part of the standard language and is not enabled by -O3.
If you want only some of it: -fno-signed-zeros, -freciprocal-math, -fassociative-math (gfortran) and Flang’s
-freal-sum-reassociation, -fno-signed-zeros, -freciprocal-math and -fapprox-func select one effect at a time;
and -fprotect-parens (listed by both gfortran --help=fortran and flang --help) makes the optimizer honor the parentheses the programmer wrote. The numerics are covered in
Floating-Point and IEEE Arithmetic.
Source: GCC 16.2 manual: Options that control optimization.
Choosing flags for a build type
Memory, the stack and default kinds
Automatic arrays and the stack
Where a local array lives is processor-dependent: the standard does not say stack or heap. The two compilers differ, and the difference can be the difference between a program that runs and one that crashes.
module work_mod
use, intrinsic :: iso_fortran_env, only: real64
implicit none
contains
function mean_of_scaled(n) result(m)
integer, intent(in) :: n
real(real64) :: m
real(real64) :: tmp(n) ! automatic array: placed on the stack
integer :: i
do i = 1, n
tmp(i) = real(i, real64)
end do
m = sum(tmp) / real(n, real64)
end function mean_of_scaled
end module work_mod
program stack_demo
use, intrinsic :: iso_fortran_env, only: int64
use work_mod, only: mean_of_scaled
implicit none
print '(a, f0.1)', 'mean = ', mean_of_scaled(1000)
print '(a, f0.1)', 'mean = ', mean_of_scaled(4000000) ! 32 MB of automatic data
end program stack_demo
ulimit -s
gfortran -std=f2023 -Wall -Wextra -pedantic stack_demo.f90 -o stack_demo && ./stack_demo
gfortran -fstack-arrays stack_demo.f90 -o stack_demo && ./stack_demo; echo rc=$?
flang stack_demo.f90 -o stack_demo && ./stack_demo; echo rc=$?
8192
mean = 500.5
mean = 2000000.5
mean = 500.5
Segmentation fault
rc=139
Segmentation fault
rc=139
With the default 8 MB stack, plain gfortran runs the program (automatic arrays of unknown size go to the heap), the
same gfortran with -fstack-arrays crashes on the 32 MB array after printing the first line, and Flang crashes by default
(the comment in the source says "stack" because for Flang and for -fstack-arrays it is true). The -Ofast level
enables -fstack-arrays in gfortran, so changing the optimization level can turn a program that worked into a
segmentation fault. Fixes are ulimit -s unlimited (shell), allocatable instead of an automatic array, and
compiler flags: gfortran’s -fmax-stack-var-size=n (default 65536 bytes; larger constant-size local arrays go to static
memory unless the procedure is recursive), -frecursive (every local on the stack; cannot be combined with
-fmax-stack-var-size or -fno-automatic), -fno-automatic (every local save, which breaks recursion and threads, and
silently ignores explicit save) and, in Flang, -fno-stack-arrays (heap, the default) and -fsave-main-program
(main program variables in static memory). -fopenmp implies -frecursive in gfortran, because OpenMP threads need
their own locals.
Default kind flags
-fdefault-real-8, -fdefault-integer-8 and -fdefault-double-8 (gfortran and Flang) change the meaning of unkinded
declarations. Legacy numerical code is sometimes built that way to get double precision without editing the source.
The side effects are surprising:
program kind_flags
use, intrinsic :: iso_fortran_env, only: real64
implicit none
real :: a
double precision :: b
real(real64) :: c
a = 1.0
b = 1.0d0
c = 1.0_real64
print '(a, 3i3)', 'kinds: ', kind(a), kind(b), kind(c)
print '(a, 3i3)', 'bytes: ', storage_size(a) / 8, storage_size(b) / 8, storage_size(c) / 8
end program kind_flags
gfortran kind_flags.f90 -o kind_flags && ./kind_flags
gfortran -fdefault-real-8 kind_flags.f90 -o kind_flags && ./kind_flags
gfortran -fdefault-real-8 -fdefault-double-8 kind_flags.f90 -o kind_flags && ./kind_flags
flang -fdefault-real-8 kind_flags.f90 -o kind_flags && ./kind_flags
kinds: 4 8 8
bytes: 4 8 8
kinds: 8 16 8
bytes: 8 16 8
kinds: 8 8 8
bytes: 8 8 8
kinds: 8 16 8
bytes: 8 16 8
Under -fdefault-real-8 the double precision variable became a 16-byte real, in both compilers, unless
-fdefault-double-8 is added (gfortran); Flang additionally printed the driver warning that numeric_storage_size is
not well defined when the default kinds are inconsistent. A program that interoperates with C or reads binary files
breaks the same way. Prefer explicit kinds (see
Intrinsic Types and Kinds); keep these flags for code
you cannot edit, and never use them on a library you intend to link with other code. -freal-4-real-8 and
siblings (-freal-8-real-4, -finteger-4-integer-8) convert by kind value and have the same hazards.
Related flags: -fconvert=big-endian|little-endian|native|swap selects the byte order of unformatted files in gfortran
(Flang: -fconvert=<value>), and -frecord-marker=4|8 the record marker length.
Module files and the directory flags
A .mod file is produced for every module, next to the source by default. Two flags control where, and they are
easy to get wrong:
| Purpose | GNU Fortran 16.2 | LLVM Flang 23 |
|---|---|---|
Write module files to a directory |
|
|
Search for module files |
|
|
Search for intrinsic modules |
|
|
Self-contained module files |
not applicable |
|
The directory must already exist. gfortran prints Warning: Nonexistent include directory 'nodir' followed by the failure
to write; Flang prints error: Error writing nodir/kernel_mod.mod: No such file or directory. The module files of
different compilers are not interchangeable: a gfortran .mod is a gzip-compressed text file starting
GFORTRAN module version '16', and a Flang .mod is plain text starting !mod$ v1. Giving one compiler the other’s
module directory produces Fatal Error: File 'mod_fl/kernel_mod.mod' opened at (1) is not a GNU Fortran module file.
The consequence for builds: use a separate module directory per compiler (and per major version), as in
mkdir -p mod_gf mod_fl obj
gfortran -J mod_gf -c kernel_mod.f90 -o obj/kernel_gf.o
gfortran -I mod_gf -c bench_main.f90 -o obj/bench_gf.o
flang -module-dir mod_fl -c kernel_mod.f90 -o obj/kernel_fl.o
flang -I mod_fl -c bench_main.f90 -o obj/bench_fl.o
all four commands succeeded; compiling bench_main.f90 for gfortran without -I mod_gf fails with
Fatal Error: Cannot open module file 'kernel_mod.mod' for reading at (1): No such file or directory.
The Intel and NAG module-directory options are in their manuals (not read or run here). Build tools set these for you:
CMake’s Fortran_MODULE_DIRECTORY property and fpm’s per-compiler build folders both do exactly this. See
Modules and Submodules for dependency order.
Source: GNU Fortran 16.2 manual: Options for directory search.
Preprocessing
The Fortran standard has no preprocessor (it has include lines, 6.4 in J3/23-007r1); everything on this section is
vendor behavior. By convention .F90 runs the C-style preprocessor and .f90 does not.
program pp_demo
implicit none
#ifdef __GFORTRAN__
print '(a)', 'compiled by gfortran'
#elif defined(__flang__)
print '(a)', 'compiled by flang'
#else
print '(a)', 'compiled by another compiler'
#endif
#ifdef USE_CHECKS
print '(a)', 'checks enabled'
#endif
end program pp_demo
Saved as pp_demo.F90:
gfortran pp_demo.F90 -o pp_demo && ./pp_demo
gfortran -DUSE_CHECKS pp_demo.F90 -o pp_demo && ./pp_demo
flang pp_demo.F90 -o pp_demo && ./pp_demo
flang -DUSE_CHECKS pp_demo.F90 -o pp_demo && ./pp_demo
compiled by gfortran
compiled by gfortran
checks enabled
compiled by flang
compiled by flang
checks enabled
Saved as pp_lower.f90 instead, with no flags, gfortran treats the # lines as source and prints
Warning: Illegal preprocessor directive for each one; Flang preprocesses the file but defines neither predefined
macro, so it runs and prints compiled by another compiler; and gfortran -cpp pp_lower.f90 prints
compiled by gfortran. The rule is therefore: -cpp (both compilers) or an upper-case extension switches on predefined
macros; -nocpp switches them off; -D<macro>[=<value>] and -U<macro> define and remove macros, -I finds includes, -E
shows the result, and -M, -MD, -MF, -MT write make-style dependency files in gfortran (see
Building with Make, CMake and Meson).
The macros __GFORTRAN__ and __flang__ identify the compiler. The preprocessor is a C one: it does not understand
Fortran’s // concatenation or ' quotes, which causes real bugs in long lines. Prefer a plain-Fortran
logical, parameter constant and if over conditional compilation, and keep #ifdef for platform and library
differences. gfortran’s -fpre-include=<file> pre-includes a header in every unit.
Linking and the runtime libraries
Fortran is linked like C. Each compiler’s runtime provides the I/O, formatted output, intrinsics and error handling that a Fortran program calls, and the driver adds it to the link line. The example is the same module-and-external-subroutine program in three files, so that the symbol names and the link steps can be inspected.
module shapes_mod
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: disc_area
contains
pure function disc_area(r) result(a)
real(real64), intent(in) :: r
real(real64) :: a
a = acos(-1.0_real64) * r * r
end function disc_area
end module shapes_mod
subroutine report_area(r)
use, intrinsic :: iso_fortran_env, only: real64
use shapes_mod, only: disc_area
implicit none
real(real64), intent(in) :: r
print '(a, f0.4)', 'area = ', disc_area(r)
end subroutine report_area
program main_link
use, intrinsic :: iso_fortran_env, only: real64
implicit none
external :: report_area
call report_area(2.0_real64)
end program main_link
gfortran -std=f2023 -Wall -Wextra -pedantic -c shapes_mod.f90 legacy.f90 main_link.f90 # one compile per file
nm -C shapes_mod.o legacy.o main_link.o | grep -E ' [TU] '
gfortran shapes_mod.o legacy.o main_link.o -o app && ./app
ldd app
The symbol table (only the interesting rows) shows how the compilers name things:
0000000000000000 T __shapes_mod_MOD_disc_area
U __shapes_mod_MOD_disc_area
0000000000000000 T report_area_
0000000000000020 T main
U report_area_
and the executable ran and printed area = 12.5664. The names are not standardized. gfortran mangles a module
procedure as __module_MOD_name and appends one underscore to the name of an external procedure (report_area_; the
-fno-underscoring option removes it and -fsecond-underscore adds another), and the Fortran main program is main. Flang
produced _QMshapes_modPdisc_area for the module procedure and report_area_ for the external one. The two schemes for modules are
different, so object files of module code from different compilers cannot be linked together, while
objects containing only external procedures with bind(c) (see
Interoperability with C) or with the common trailing
underscore can. ldd shows what the executable needs at run time:
| Build | Shared libraries needed (ldd, platform ones omitted) |
|---|---|
|
|
|
|
|
|
The driver chooses the libraries for you; gfortran -### shows that it adds -lgfortran -lm and the GCC runtime, and
flang -### that it adds -lflang_rt.runtime -lm and the GCC runtime. A Fortran program that is linked by a C or C++
driver must add these itself (-lgfortran -lm or the Flang equivalents), which is the commonest link error in mixed
builds (undefined reference to _gfortran_st_write). Libraries are searched in order, so put them after the objects
that need them: gfortran main_link.o -L. -lshapes -o app_lib works (the archive was built with ar rcs libshapes.a
shapes_mod.o legacy.o) while the reverse order does not resolve. A shared library needs position-independent code:
gfortran -fPIC -shared shapes_mod.f90 legacy.f90 -o libshapes.so exported __shapes_mod_MOD_disc_area and
report_area_ (checked with nm -D). Fortran code that uses coarrays also needs the coarray library: gfortran 16 takes
-fcoarray=lib and links libcaf_shmem, and its valid values are none, single and lib only (see
Coarrays, Images and Synchronization);
Flang has an experimental -fcoarray. For BLAS and LAPACK see
BLAS, LAPACK and Numerical Libraries.
Provenance: compiler_version and compiler_options
When a user reports a numerical difference, the first question is what built the program.
The intrinsic module iso_fortran_env provides compiler_version() and compiler_options() (J3/23-007r1, 16.10.2.6 and
16.10.2.7); both return a processor-dependent string that "should include relevant information that could be useful for
diagnosing problems at a later date".
program provenance
use, intrinsic :: iso_fortran_env, only: compiler_version, compiler_options
implicit none
print '(2a)', 'version: ', compiler_version()
print '(2a)', 'options: ', compiler_options()
end program provenance
gfortran -std=f2023 -Wall -O2 provenance.f90 -o provenance && ./provenance
gfortran -O3 -march=native provenance.f90 -o provenance && ./provenance
flang -O2 -g provenance.f90 -o provenance && ./provenance
version: GCC version 16.2.0
options: -mlittle-endian -mabi=lp64 -O2 -Wall -std=f2023 -fpre-include=/usr/include/finclude/aarch64-linux-gnu/math-vector-fortran.h
version: GCC version 16.2.0
options: -mlittle-endian -mabi=lp64 -mcpu=generic-armv8-a+flagm2+lse+dotprod+rdma+crc+aes+sha3+fp16fml+jscvt+fcma+rcpc2+frintts+i8mm+bf16+sb+pauth -O3 -fpre-include=/usr/include/finclude/aarch64-linux-gnu/math-vector-fortran.h
version: Debian flang version 23.1.4
options: -O2 -g -o provenance
Two observations. gfortran lists the options it kept, not the command line: -march=native has been replaced by the
expanded -mcpu=... string and the -pedantic and -Wextra options are not in the first line, because
only the options that affect the generated code and the dialect are recorded. A line like this in a log is enough to
reproduce a build. The Flang string is the command line, including -o provenance. Print both in the banner of a
program and in the header of every output file; the heatflow CSV writer does this in its comment line.
Source: J3/23-007r1, 16.10.2.6 COMPILER_OPTIONS and 16.10.2.7 COMPILER_VERSION.
Vendor extensions and staying portable
The standard allows a processor to accept additional forms and relationships as long as they do not conflict with the
standard ones (4.2), and every compiler does. A program that uses them is not standard-conforming and fails on the
next compiler. Both compilers publish their lists: the GNU Fortran manual’s "Extensions" chapter has a list of
"Extensions implemented in GNU Fortran" and one of "Extensions not implemented in GNU Fortran" (abbreviated GF
Extensions in the outline of this guide), and the Flang documentation page "Fortran Extensions supported by Flang"
(Extensions.html, FN Extensions) groups them as intentional violations of the standard, default-on extensions,
opt-in extensions and unsupported features. The first gives a sense of how permissive each tool is by default:
program ext_demo
implicit none
real*8 :: x
integer :: i
double precision :: y
x = 1.0d0
y = 2.0d0
i = 3
print *, x + y + i
print *, 'tab here'
end program ext_demo
gfortran -std=f2023 -c ext_demo.f90
gfortran -std=gnu -Wall -pedantic -c ext_demo.f90
flang -pedantic -c ext_demo.f90
ext_demo.f90:3:9:
3 | real*8 :: x
| 1
Error: GNU Extension: Nonstandard type declaration REAL*8 at (1)
ext_demo.f90:6:3:
6 | x = 1.0d0
| 1
Error: Symbol 'x' at (1) has no IMPLICIT type
ext_demo.f90:3:9:
3 | real*8 :: x
| 1
Warning: GNU Extension: Nonstandard type declaration REAL*8 at (1)
ext_demo.f90:3:7: portability: nonstandard usage: TYPE*KIND syntax
real*8 :: x
^^^
ext_demo.f90:3:3: in the context: intrinsic type spec
real*8 :: x
^
ext_demo.f90:2:3: in the context: specification part
implicit none
^
The three blocks are gfortran under -std=f2023 (an error, and the follow-on error because the declaration is dropped),
gfortran under -std=gnu with -pedantic (a warning), and Flang with -pedantic (a portability warning; without it
Flang accepts real*8 silently). The second example is about intrinsic procedures that the standard does not define:
program gnu_intrinsic
use, intrinsic :: iso_fortran_env, only: int32
implicit none
integer(int32) :: pid
pid = getpid() ! GNU extension, not a standard intrinsic
print '(a)', merge('pid is positive', 'pid is odd ', pid > 0)
end program gnu_intrinsic
gfortran -std=f2023 -c gnu_intrinsic.f90
gfortran -std=f2023 -fall-intrinsics gnu_intrinsic.f90 -o gnu_intrinsic && ./gnu_intrinsic
flang -pedantic gnu_intrinsic.f90 -o gnu_intrinsic && ./gnu_intrinsic
gnu_intrinsic.f90:5:8:
5 | pid = getpid() ! GNU extension, not a standard intrinsic
| 1
Error: Function 'getpid' at (1) has no IMPLICIT type
pid is positive
pid is positive
Under -std=f2023 the GNU intrinsic is not recognized (with -Wintrinsics-std the compiler says it "will be treated as
if declared EXTERNAL"); -fall-intrinsics brings it back, and Flang accepts it with no warning even under
-pedantic, since it too provides getpid. The same is true of tab characters in free-form source: standard
conformance does not allow them, gfortran under -std=f2023 warns for every one (Nonconforming tab character at (1)
[-Wtabs]) and Flang accepts them silently, even with -pedantic.
The practical recipe for portable code:
-
build with
-std=f2023 -pedantic -Wall -Wextraon gfortran and without any special option on Flang, in CI; -
never use
real*8,integer*4,double precisionin new code (useiso_fortran_envkinds), non-standard intrinsics,%val, Cray pointers (-fcray-pointer,-funsafe-cray-pointers), DEC structures (-fdec-structure,-fdec),-fbackslash,-fdollar-ok, hollerith constants or.xor.; -
use
iso_c_binding,iso_fortran_envand theieee_*modules for everything the old extensions provided; -
put a vendor extension that you cannot avoid behind a module, so one file changes on the next compiler;
-
label the minimum compiler versions in the README and test them.
The Flang options that make a legacy extension available are opt-in and named after the extension:
-fbackslash, -flogical-abbreviations (.t., .f.), -fxor-operator (.xor.), -falternative-parameter-statement,
-funsigned and -fimplicit-none-ext; gfortran groups the DEC-compatible ones under -fdec (-fdec-structure,
-fdec-math, -fdec-include, -fdec-static, -fdec-blank-format-item and others). -funsigned in both
compilers enables the experimental unsigned integer type (a Fortran 202y item, see
What’s New and Fortran 2028 Preview).
Also worth a look is -fc-prototypes, which makes gfortran print a C prototype for each bind(c) interface.
Recommended option sets
| Build | gfortran 16.2 (all flags run) | Flang 23 and others |
|---|---|---|
Developer and unit test |
|
Flang builds with |
CI gate |
the above plus |
same Flang command, with |
Release |
|
|
Cluster, measured |
|
the same with |
The same choices expressed through CMake, Meson and fpm are in
Building with Make, CMake and Meson and
Fortran Package Manager (fpm) (fpm has --profile debug and --profile release
and --flag for extra options); gfortran --help=fortran and flang --help are the reference when one of these
flags is in doubt.
Where the books are outdated
-
Option names. Older textbooks teach
-fbounds-check,-Mbounds,-ffloat-store,-pgas the way to debug and tune.-fbounds-checkand-fcheck-array-temporariesare deprecated aliases of-fcheck=boundsand-fcheck=array-tempsin GNU Fortran;-fcheck=allis the complete form. -
Compiler identities. Pre-2025 material names
ifort(discontinued in oneAPI 2025.0 in favor ofifx),flang-new(renamedflangin LLVM 20), and old NAG releases. Option lists written for those still mostly apply toifxand Flang, but check each one with--help. -
Standard level.
-std=f95and-std=f2003are what the old books assume; the current level is-std=f2023, with-std=f202yfor experiments. -
Build tools. Books describe
make; fpm, CMake and Meson now set most of these flags for you. -
Stack and kind flags.
-fno-automaticand-fdefault-real-8are legacy g77 habits. They still exist and still have side effects (above). -
Debugging. Traceback is on by default in gfortran; and
-gis what produces line numbers.
Standard version provenance and compiler support
| Item | Where defined | Compiler support |
|---|---|---|
Conformance obligations of a processor, extensions allowed |
J3/23-007r1, 4.2 |
all |
|
16.10.2.6, 16.10.2.7 ( |
gfortran 16.2 and Flang 23 (run above) |
|
Fortran 2023 (named so by gfortran’s diagnostic), 16.9 |
gfortran 16.2 and Flang 23 (run above) |
|
8.7 |
gfortran and Flang ( |
Deleted and obsolescent feature lists |
Annex B, 4.4 |
compilers report some under |
|
processor-dependent, not in the standard |
gfortran only (Flang: |
|
not in the standard |
both |
Module file format, symbol mangling |
not in the standard |
incompatible between gfortran and Flang (shown above) |
Not checked here, because no compiler for them is installed: Intel ifx, NAG, HPE Cray and NVIDIA HPC compilers; see History, Standards and Compilers for the support table.
Pitfalls
-
The default is
-std=gnu. A cleangfortran file.f90proves nothing about portability; add-std=f2023 -pedantic. -
-Wallis not all.-Wextra,-Wconversion-extra,-Wimplicit-interface,-Wuse-without-onlyand-Wrealloc-lhsare separate. -
Flang has no checking build. No bounds, uninitialized-variable or exception-trap flags exist in 23.1; do not assume a Flang build is a debugging build.
-
Documentation is not versioned like the compiler. The unversioned GNU Fortran manual describes the development branch (
-fcoarray=shared,-Wundefined-varsdo not exist in 16.2). -
-finit-local-zerohides bugs. Use-finit-real=snanfor finding them. -
-O3and-Ofastchange the stack.-fstack-arrayscan turn a working program into a segmentation fault with the same source. -
-ffast-mathremoves NaN checks.ieee_is_nanreturns false for a real NaN; keep it out of any code that tests for NaN or relies onieee_arithmetic. -
-march=nativeproduces a non-portable binary. -
-fdefault-real-8turnsdouble precisioninto 16 bytes. -
Module files and objects of different compilers do not mix. Use a separate directory (and separate libraries) per compiler.
-
Link errors with C drivers. Add
-lgfortran -lm(or the Flang runtime) when the final link is not done by the Fortran driver. -
-Jneeds an existing directory. -
The preprocessor depends on the extension.
.f90with#ifdeflines is not preprocessed by gfortran. -
A runtime check can change timing and hide a race. Test with and without
-fcheck=all.
See also Best Practices and Style and Common Gotchas and Pitfalls.
References
-
GNU Fortran 16.2 manual: Invoking GNU Fortran, Options controlling Fortran dialect, Options controlling Fortran preprocessing, Options to request or suppress errors and warnings, Options for debugging your program or GNU Fortran, Options for directory search, Options for influencing the linking step, Options for code generation conventions, Runtime options and Extensions
-
GCC 16.2 manual: Options that control optimization and Options for linking
-
Flang documentation: Flang Drivers, Fortran Extensions supported by Flang, Fortran Standards Support, Option Comparison and Module Files
-
Fortran Programming Language: Building programs, Compiling source code, Linking pieces together and Runtime libraries