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=shared and -Wundefined-vars, and 16.2 rejects both (gfortran: error: Unrecognized option: 'shared', with the note that the valid arguments to -fcoarray= are lib 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 (the Fortran-2023-status.html address 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 in FortranStandardsSupport.html and the extensions in Extensions.html. The executable is flang (it was flang-new before LLVM 20; the Debian package used here installs flang-23, which the examples invoke).

  • Intel’s compiler is ifx. The classic ifort was discontinued in oneAPI 2025.0, so older options lists and answers that say ifort describe 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=optimizers and flang --help printed 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:

flowchart LR SRC["source file
.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

-E

preprocessing

source text on standard output (-P removes #line markers)

-fsyntax-only

parsing and semantic checks

no object file; a fast check

-S

compilation

assembly (.s)

-c

assembly

an object file (.o) and the module files of the program units in it

none of these

linking

an executable (a.out unless -o names it)

-###

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

-v

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)

-std=f95, -std=f2003, -std=f2008, -std=f2018, -std=f2023

conform to that edition; features from a later edition and GNU extensions are errors or warnings

-std=f2008ts

Fortran 2008 including the TS 29113 features (the C-interoperability descriptors)

-std=f202y

"experimental Fortran 202y features", the working name of the revision after 2023

-std=gnu

the default: the standard language plus the GNU extensions

-std=legacy

accept extensions to support legacy code

-pedantic, -Wpedantic

warn about the use of extensions to the selected standard, and make the GNU extensions an error under a -std=f... option; -pedantic-errors makes them errors in every mode

-fall-intrinsics

all intrinsic procedures are available whatever -std says

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

-Wall

-Waliasing, -Wampersand, -Wconversion, -Wsurprising, -Wc-binding-type, -Wintrinsics-std, -Wtabs, -Wintrinsic-shadow, -Wline-truncation, -Wtarget-lifetime, -Winteger-division, -Wreal-q-constant, -Wunused (which includes -Wunused-variable, -Wunused-dummy-argument and others) and -Wundefined-do-loop; the manual also states that -Wunused-dummy-argument, -Wzerotrip and -Wexternal-argument-mismatch are implied by -Wall; the middle-end -Wuninitialized and -Wmaybe-uninitialized come with it when optimization is on

-Wextra

-Wcompare-reals, -Wunused-parameter (only together with -Wunused or -Wall), -Wdo-subscript and -Wfunction-elimination

-pedantic

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

-Wconversion-extra

implicit conversions between types and kinds that -Wconversion leaves silent (it does not imply -Wconversion)

-Wimplicit-interface

a call to a procedure with no explicit interface; it does not check consistency across program units

-Wimplicit-procedure

a call to a procedure that is neither explicitly declared nor external

-Wuse-without-only

a use statement with no only list, which imports every public name

-Wrealloc-lhs, -Wrealloc-lhs-all

intrinsic assignment that may insert (re)allocation code for the allocatable left-hand side; -Wrealloc-lhs-all includes scalars

-Warray-temporaries

array temporaries created at a call or in an expression, so that they can be removed from hot loops

-Waliasing

possible aliasing of dummy arguments (in -Wall)

-Wcharacter-truncation

character expressions that are truncated on assignment

-Wunderflow, -Wintrinsic-shadow, -Wtarget-lifetime

constant underflow, a user procedure named like an intrinsic, a pointer that may outlive its target

-Werror

turns every warning into an error; each -W... option has a -Wno-... form

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

-std=f2023 -pedantic

-std=f2018 (the only value), -pedantic

ifx -stand (Intel manual), NAG one switch per edition (NAG manual)

Most warnings

-Wall -Wextra

defaults, no -Wall

ifx -warn all (named in the issue baseline for this section, vendor page not readable here)

Warnings as errors

-Werror

-Werror

vendor manuals

Runtime checks

-fcheck=all

none

ifx -check all, NAG -C=all (named in the issue baseline for this section)

Traceback on error

default; -g for lines; -fno-backtrace disables

none, no -fbacktrace

ifx -traceback

Floating-point traps

-ffpe-trap=...

none

vendor manuals

Initialize locals

-finit-real=snan -finit-integer=n …​

none (-finit-global-zero is for globals)

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

all

every check below

array-temps

a run-time warning once per call site that creates an array temporary for an actual argument

bits

invalid arguments to the bit-manipulation intrinsics

bounds

array subscripts against declared bounds, and indices of assumed-shape and deferred-shape arrays; also string lengths in some array constructors

do

a loop variable modified inside its do loop

mem

memory allocation failures (an explicit allocate is always checked)

pointer

references through disassociated pointers and unallocated allocatables

recursion

a call that recurses into a procedure that is not recursive

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

-O0

no optimization, fastest compile, best for debuggers (default)

same

-O1, -O2

progressively more; -O2 is the usual release level

same

-O3

more aggressive optimization

same

-Os, -Og

optimize for size, and for the debugging experience (GCC level options)

see flang --help

-Ofast

-O3 -ffast-math and -fstack-arrays (the latter unless -fmax-stack-var-size is given)

deprecated: the help text says to use -O3 -ffast-math -fstack-arrays -fno-protect-parens

-march=native, -mtune=native

compile for the build host’s CPU (x86; on this aarch64 container -march=native was expanded to a long -mcpu=... string by the driver)

-march=<value> and -mtune=<value>

-flto

link-time optimization across files

-flto, -flto=<value>; the LTO link failed in the test container because the linker plugin LLVMgold.so was missing, so Flang LTO is not run here

-funroll-loops, -fvectorize

-funroll-loops (off by default)

-funroll-loops, -fvectorize, -fslp-vectorize

-ffast-math

see below

see below

-fopenmp, -fopenacc

OpenMP, OpenACC, and OpenMP implies -frecursive

-fopenmp, -fopenacc

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

-O0

3.60

-O0

2.69

-O2

0.59

-O2

0.59

-O3

0.33

-O3

0.48

-O3 -march=native

0.35

-O3 -march=native

0.51

-O2 -fcheck=all

0.63

-O3 -flto

link failed, not run

-O3 -march=native -ffast-math

0.31

-O3 -flto

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

flowchart TD A["What is this build for?"] -->|"developing, debugging"| D["-O0 -g -std=f2023 -Wall -Wextra -pedantic -fcheck=all -ffpe-trap=invalid,zero,overflow -finit-real=snan"] A -->|"CI, strict"| C["developer flags plus -Werror, and a second compiler (Flang) with its defaults"] A -->|"release for many machines"| R["-O2 -g, no -march=native, no -ffast-math"] A -->|"private cluster build"| H["-O3 -march=native after measuring, document the CPU"]

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

-J<dir> (the directory is also searched)

-module-dir <dir>; -J <dir> is accepted as an alias

Search for module files

-I <dir>

-I <dir>

Search for intrinsic modules

-fintrinsic-modules-path <dir>

-fintrinsic-modules-path <dir>

Self-contained module files

not applicable

-fhermetic-module-files

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)

gfortran

libgfortran.so.5, libm.so.6, libgcc_s.so.1, libc.so.6

gfortran -static-libgfortran -static-libgcc

libm.so.6, libc.so.6 only

flang

libm.so.6, libc.so.6 only: the Flang runtime (libflang_rt.runtime) is linked statically by default

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 -Wextra on gfortran and without any special option on Flang, in CI;

  • never use real*8, integer*4, double precision in new code (use iso_fortran_env kinds), 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_env and the ieee_* 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.

Build gfortran 16.2 (all flags run) Flang 23 and others

Developer and unit test

-std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all -g -O0 -ffpe-trap=invalid,zero,overflow -finit-real=snan

Flang builds with -g -O0 as a second opinion on the diagnostics; ifx -stand f23 -warn all -check all -traceback and NAG -C=all are the checking builds named by the vendors (not run here)

CI gate

the above plus -Werror and -Wconversion-extra -Wimplicit-interface -Wuse-without-only for library code

same Flang command, with -Werror

Release

-O2 -g (plus -fopenmp or -fcoarray=lib when used), no -march=native, no -ffast-math

-O2

Cluster, measured

-O3 -march=native after a timing test on that machine

the same with -march=/-mtune=

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, -pg as the way to debug and tune. -fbounds-check and -fcheck-array-temporaries are deprecated aliases of -fcheck=bounds and -fcheck=array-temps in GNU Fortran; -fcheck=all is the complete form.

  • Compiler identities. Pre-2025 material names ifort (discontinued in oneAPI 2025.0 in favor of ifx), flang-new (renamed flang in LLVM 20), and old NAG releases. Option lists written for those still mostly apply to ifx and Flang, but check each one with --help.

  • Standard level. -std=f95 and -std=f2003 are what the old books assume; the current level is -std=f2023, with -std=f202y for experiments.

  • Build tools. Books describe make; fpm, CMake and Meson now set most of these flags for you.

  • Stack and kind flags. -fno-automatic and -fdefault-real-8 are legacy g77 habits. They still exist and still have side effects (above).

  • Debugging. Traceback is on by default in gfortran; and -g is 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

compiler_version, compiler_options

16.10.2.6, 16.10.2.7 (iso_fortran_env)

gfortran 16.2 and Flang 23 (run above)

sind and the other degree-argument trigonometric functions

Fortran 2023 (named so by gfortran’s diagnostic), 16.9

gfortran 16.2 and Flang 23 (run above)

implicit none (type, external)

8.7

gfortran and Flang (-fimplicit-none-ext)

Deleted and obsolescent feature lists

Annex B, 4.4

compilers report some under -std=f2023

-std= levels, -fcheck=, -ffpe-trap=, -finit-*

processor-dependent, not in the standard

gfortran only (Flang: -std=f2018 only; no checks)

-ffast-math, -O*, -march=

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 clean gfortran file.f90 proves nothing about portability; add -std=f2023 -pedantic.

  • -Wall is not all. -Wextra, -Wconversion-extra, -Wimplicit-interface, -Wuse-without-only and -Wrealloc-lhs are 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-vars do not exist in 16.2).

  • -finit-local-zero hides bugs. Use -finit-real=snan for finding them.

  • -O3 and -Ofast change the stack. -fstack-arrays can turn a working program into a segmentation fault with the same source.

  • -ffast-math removes NaN checks. ieee_is_nan returns false for a real NaN; keep it out of any code that tests for NaN or relies on ieee_arithmetic.

  • -march=native produces a non-portable binary.

  • -fdefault-real-8 turns double precision into 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.

  • -J needs an existing directory.

  • The preprocessor depends on the extension. .f90 with #ifdef lines is not preprocessed by gfortran.

  • A runtime check can change timing and hide a race. Test with and without -fcheck=all.