Interoperability with C
|
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 Fortran
example was compiled warning-free with gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all
(GNU Fortran 16.2) and run, each C file was compiled with gcc -std=c17 -Wall -Wextra -pedantic (GCC 16.2), and the
examples were also built and run with LLVM Flang 23 (23.1.4), in a Debian container on an AArch64 (64-bit Arm) machine.
The output shown is real output; where the two compilers differ, or reject a feature, the page says so and shows the
real diagnostics. Sizes and kind values are the ones of that platform (Linux, LP64 data model). Interoperability with C is
clause 18 of the standard: 18.2 is the iso_c_binding module, 18.3 says which Fortran entities interoperate with which C
entities, 18.9 covers global variables and 18.10 procedures. The bind attribute is in 8.5.5 and in 15.6.2.1
(proc-language-binding-spec), value in 8.5.19.
Fortran has had a standard way to call C, and to be called from C, since Fortran 2003: the iso_c_binding module
supplies the kinds, pointer types and conversion procedures, and the bind(c) attribute gives a procedure, variable
or derived type the C view of itself. Both directions need nothing but the compilers' normal object files. Fortran
2018 absorbed the technical specification ISO/IEC TS 29113 and relaxed most of the restrictions of the first design
(assumed-type, optional, allocatable and assumed-shape dummy arguments; the latter two through C descriptors, the
subject of
C Descriptors and ISO_Fortran_binding),
and Fortran 2023 added f_c_string, c_f_strpointer and the lower argument of c_f_pointer. This page covers the
data and procedures that cross the boundary as plain C values and pointers, using the running example heatflow, whose
Jacobi and FTCS kernels are exposed to C through the module heatflow_capi and the header heatflow.h. How to build
that library for C, C++ and Python callers is in
Calling Fortran from C, C++ and Python, and
the same ideas for a MATLAB gateway in
MATLAB: MEX, Engine and MAT-File APIs. The C
language side (pointers, structures, linkage) is described in
C Pointers,
Structures, Unions and Type Aliases and
Storage Duration, Scope and Linkage; the same
boundary seen from Rust is in FFI & C Interop and from Go in
cgo and C Interoperability.
The model: four pieces and a companion processor
The standard speaks of a companion processor: the C compiler whose types and calling conventions the Fortran processor agrees to follow. Interoperability is then a statement about entities, not about languages. A Fortran entity is interoperable if an equivalent C entity could be written (18.1), whether or not one exists. A call across the boundary involves four pieces (18.3.7, Note 4): the reference in the caller, the interface or prototype the caller sees, the interface or prototype the callee is described by, and the function itself. The compiler can check only the pieces it sees, so a wrong interface body is a silent error, not a diagnostic: nothing compares a Fortran interface body with a C definition.
| Direction | What you write |
|---|---|
Fortran calls C |
an interface block describing the C function with |
C calls Fortran |
a Fortran procedure with |
C data seen from Fortran |
a module variable with |
Fortran data seen from C |
the same declarations read the other way; arrays are passed as the address of the first element |
The rules for the arguments of an interoperable procedure (15.6.2.1, C1561-C1568) are short. A function result must be
an interoperable scalar. Each dummy argument must be an interoperable variable or procedure, or, since Fortran 2018,
assumed-shape, assumed-rank, assumed-type, of assumed character length, allocatable or a pointer; with the latter group
the C function receives a pointer to a descriptor, not to the data (18.3.7). A dummy argument cannot be a coarray,
and an array dummy argument cannot have value. An interoperable procedure cannot be elemental, optional and value
cannot both be given to a dummy argument of the same procedure (C869), and a C function with a variable argument list
(...) cannot be described (18.3.7, item 7); Fortran has no portable way to call printf.
The iso_c_binding kinds and constants
Fortran’s default integer is not guaranteed to be a C int, and real need not be a C float. The kinds that mean
a specific C type live in iso_c_binding (18.2.2, Table 18.2). Each C type has a named constant for the kind of the
interoperating Fortran type, and a program uses the constant rather than a number:
| C type | Fortran declaration | Notes |
|---|---|---|
|
|
the sizes are the platform’s |
|
|
there are no unsigned kinds, so |
|
|
|
|
|
exact-width kinds are the portable choice for data formats |
|
|
a negative value means that no Fortran kind matches |
|
|
same values as the real kinds |
|
|
not the default |
|
|
length must be 1 for a scalar variable; arrays of |
|
|
opaque; see C pointers and function pointers |
function pointers |
|
opaque |
If the companion processor has a type without a matching Fortran kind, the constant is negative. For the real kinds,
-1 means that no Fortran real kind has the precision of the C type, -2 that none has its range, -3 that none has either and -4 that
there is no interoperating kind for another reason; for the integer kinds, -1 means that the C type exists and has no Fortran
kind and -2 that the C type does not exist (18.2.2). Test with if (c_long_double > 0) before
relying on a constant that might be negative. The program below prints what the two compilers report on this machine:
program c_kinds
use, intrinsic :: iso_c_binding
implicit none
print '(a, i0)', 'c_int = ', c_int
print '(a, i0)', 'c_short = ', c_short
print '(a, i0)', 'c_long = ', c_long
print '(a, i0)', 'c_long_long = ', c_long_long
print '(a, i0)', 'c_signed_char = ', c_signed_char
print '(a, i0)', 'c_size_t = ', c_size_t
print '(a, i0)', 'c_int8_t = ', c_int8_t
print '(a, i0)', 'c_int64_t = ', c_int64_t
print '(a, i0)', 'c_intptr_t = ', c_intptr_t
print '(a, i0)', 'c_float = ', c_float
print '(a, i0)', 'c_double = ', c_double
print '(a, i0)', 'c_long_double = ', c_long_double
print '(a, i0)', 'c_float_complex = ', c_float_complex
print '(a, i0)', 'c_bool = ', c_bool
print '(a, i0)', 'c_char = ', c_char
print '(a, l1, a, l1)', 'c_bool true/false stored as logical: ', logical(.true., c_bool), ' ', logical(.false., c_bool)
print '(a, i0, a, i0, a, i0)', 'c_sizeof(1_c_int)=', c_sizeof(1_c_int), ' c_sizeof(1.0_c_double)=', c_sizeof(1.0_c_double), &
' c_sizeof(1.0_c_long_double)=', c_sizeof(1.0_c_long_double)
print '(a, 8(i0, 1x))', 'codes null alert backspace formfeed newline cr htab vtab: ', &
iachar(c_null_char), iachar(c_alert), iachar(c_backspace), iachar(c_form_feed), iachar(c_new_line), &
iachar(c_carriage_return), iachar(c_horizontal_tab), iachar(c_vertical_tab)
print '(a, l1, a, l1)', 'c_associated(c_null_ptr)=', c_associated(c_null_ptr), ' c_associated(c_null_funptr)=', c_associated(c_null_funptr)
end program c_kinds
GNU Fortran 16.2 and LLVM Flang 23 printed the same lines (the first run is the GNU Fortran one):
c_int = 4
c_short = 2
c_long = 8
c_long_long = 8
c_signed_char = 1
c_size_t = 8
c_int8_t = 1
c_int64_t = 8
c_intptr_t = 8
c_float = 4
c_double = 8
c_long_double = 16
c_float_complex = 4
c_bool = 1
c_char = 1
c_bool true/false stored as logical: T F
c_sizeof(1_c_int)=4 c_sizeof(1.0_c_double)=8 c_sizeof(1.0_c_long_double)=16
codes null alert backspace formfeed newline cr htab vtab: 0 7 8 12 10 13 9 11
c_associated(c_null_ptr)=F c_associated(c_null_funptr)=F
On this platform c_long_double is 16 because AArch64 Linux has a 128-bit long double that matches Fortran’s quad
precision; on another platform long double can be a different format and the constant, or its sign, will differ, which
is the reason to print or test it instead of assuming. c_bool is a one-byte logical whose true value
has the C value 1 (18.2.2): a logical(c_bool) argument can be passed to a C function that takes _Bool without
conversion, and a default logical cannot (see What the compiler checks). The c_null_char constant and its
companions are the characters of Table 18.1, here printed as their character codes; c_new_line equals new_line('a').
The exact-width constants are the ones to use when a binary file or a protocol fixes the size. The constants
c_float128 and c_float16 are extensions, not standard: Flang documents c_float128 and c_float16 in its extensions
list, and GNU Fortran 16.2 accepts c_float128 without -std but rejects it under -std=f2023.
Calling C from Fortran
A C function is described to Fortran by an interface body with bind(c). The body has the same shape as for any
external procedure (see
Interfaces, Generics and Operator Overloading),
with three differences: the dummy arguments have interoperable types; a scalar argument that C receives by value has the
value attribute; and a scalar that C receives as a pointer does not. With the value attribute the dummy argument is
passed as a copy, as in C, and the actual argument may be an expression; without it, the compiler passes the address. The
intent of a value dummy argument may be only in or omitted (C868). The import statement brings the kind names
into the interface body, which does not see its host’s names automatically.
The C side of the first program below has three small functions, one with an unusual case in its name:
/* C side of the first calls from Fortran. */
int hf_add_value(int a, int b) { return a + b; }
void hf_add_ref(const int *a, const int *b, int *sum) { *sum = *a + *b; }
double HF_Mixed_Case(double x) { return 2.0 * x; }
program c_calls
use, intrinsic :: iso_c_binding, only: c_int, c_double
implicit none
interface
! Fortran name and C name differ: name= gives the binding label, whose case is significant.
function add_by_value(a, b) bind(c, name='hf_add_value') result(s)
import :: c_int
integer(c_int), value, intent(in) :: a, b ! passed like a C int
integer(c_int) :: s
end function add_by_value
subroutine add_by_reference(a, b, s) bind(c, name='hf_add_ref')
import :: c_int
integer(c_int), intent(in) :: a, b ! no value: C receives int *
integer(c_int), intent(out) :: s
end subroutine add_by_reference
function twice(x) bind(c, name='HF_Mixed_Case') result(y)
import :: c_double
real(c_double), value, intent(in) :: x
real(c_double) :: y
end function twice
! A function of the C library, no C source of our own needed.
function c_hypot(x, y) bind(c, name='hypot') result(h)
import :: c_double
real(c_double), value, intent(in) :: x, y
real(c_double) :: h
end function c_hypot
end interface
integer(c_int) :: total
print '(a, i0)', 'by value: 3 + 4 = ', add_by_value(3_c_int, 4_c_int)
call add_by_reference(3_c_int, 4_c_int, total)
print '(a, i0)', 'by reference: 3 + 4 = ', total
print '(a, f4.1)', 'HF_Mixed_Case(1.5) = ', twice(1.5_c_double)
print '(a, f4.1)', 'hypot(3, 4) = ', c_hypot(3.0_c_double, 4.0_c_double)
end program c_calls
Built with gcc -fPIC -c 03_ops.c and gfortran 03_calls.f90 03_ops.o (strict flags as above), the program printed the
following; Flang 23 gave identical output:
by value: 3 + 4 = 7
by reference: 3 + 4 = 7
HF_Mixed_Case(1.5) = 3.0
hypot(3, 4) = 5.0
Three things happen here. First, name= sets the binding label (18.10.2): the name by which the procedure is known to
the C linker. Without name= the label is the Fortran name in lower case, so bind(c) on a procedure Add_Values
would be called add_values in C. The case of a label given with name= is significant, which is why HF_Mixed_Case can
be reached from a Fortran interface called twice. Second, hf_add_value has value arguments because the C
prototype takes int, while hf_add_ref takes int * and is described without value. Third, hypot shows that the
interface body does not care who wrote the function: any symbol the linker can find, including those of the C library,
can be described this way, and the interface can use a Fortran name of its own. A C function that returns void is a
subroutine; any other return type is a function with an interoperable scalar result.
A mistake in the value attribute does not produce a diagnostic. Writing the interface of hf_add_value without value
(integer(c_int), intent(in) for both a and b) compiled warning-free with the strict flags, and the program printed
3 + 4 = 8394212 on three runs in a row: the compiler passed the addresses of the two integers where C expects the
integers themselves, and C added two addresses, truncated to an int. The opposite mistake, value on the third argument of
hf_add_ref, which C takes as int *, also compiled without a message; the call made C store through the address 0 and the
program ended with Program received signal SIGSEGV: Segmentation fault - invalid memory reference. (the two interface
bodies were the ones of the first program with value removed from a and b, and with value added to s). Check every interface body against the C prototype,
argument by argument, as 18.3.7 lists.
What the compiler checks
The compiler does check that the dummy arguments and the result are interoperable types (C1562-C1567), and so it can
reject an interface that no C prototype could match. This deliberately non-conforming module contains a
dummy argument of default logical, one of a derived type that lacks bind(c) and one of default real:
! Deliberately non-conforming: arguments that cannot be interoperable.
module bad_binding
use, intrinsic :: iso_c_binding, only: c_int
implicit none
type :: plain_t ! no bind(c): not interoperable
integer(c_int) :: id
end type plain_t
contains
subroutine set_flag(flag) bind(c)
logical, value, intent(in) :: flag ! default logical has no C counterpart; c_bool is required
end subroutine set_flag
subroutine take_plain(p) bind(c)
type(plain_t), intent(in) :: p ! derived type without bind(c)
end subroutine take_plain
subroutine take_default_real(x) bind(c)
real, value, intent(in) :: x ! default real is c_float only by coincidence of the kind value
end subroutine take_default_real
end module bad_binding
GNU Fortran 16.2 with -std=f2023 reports a warning for the kind it cannot prove to be a C type and errors for the
others:
11_bad_interface.f90:12:23:
12 | subroutine set_flag(flag) bind(c)
| 1~~~
Warning: Variable 'flag' at (1) is a dummy argument of the BIND(C) procedure 'set_flag' but may not be C interoperable [-Wc-binding-type]
11_bad_interface.f90:20:32:
20 | subroutine take_default_real(x) bind(c)
| 1
Warning: Variable 'x' at (1) is a dummy argument of the BIND(C) procedure 'take_default_real' but may not be C interoperable [-Wc-binding-type]
11_bad_interface.f90:16:25:
16 | subroutine take_plain(p) bind(c)
| 1
Error: Variable 'p' at (1) is a dummy argument to the BIND(C) procedure 'take_plain' but is not C interoperable because derived type 'plain_t' is not C interoperable
11_bad_interface.f90:12:23:
12 | subroutine set_flag(flag) bind(c)
| 1~~~
Error: GNU Extension: LOGICAL dummy argument 'flag' at (1) with non-C_Bool kind in BIND(C) procedure 'set_flag'
LLVM Flang 23 compiled the same file without a message and exit code 0 (the line flang rc=0 was added by the test
script), so the diagnostic depends on the compiler, and the module that passed one compiler’s checks is not
proven interoperable. Treat the -Wc-binding-type warning of GNU Fortran as an error in a project that exposes a C API
(-Werror=c-binding-type). The warning for default real is a fair reminder: the kind of default real happens to
be that of c_float on most platforms, but only real(c_float) says so. The default logical is a hard error under
-std=f2023, because a four-byte .true. is not the C _Bool.
Binding labels are global identifiers: two entities cannot have the same one, and a binding label must also differ from the global identifier of any other global entity, such as an external procedure without a label, ignoring differences in case (19.2). This module is deliberately wrong:
! Deliberately non-conforming: two procedures with the same binding label.
module dup_label
implicit none
contains
subroutine first() bind(c, name='hf_step')
end subroutine first
subroutine second() bind(c, name='hf_step')
end subroutine second
end module dup_label
11_dup.f90:5:3:
5 | subroutine first() bind(c, name='hf_step')
| 1~~~~~~~~~~~~~~~
6 | end subroutine first
7 | subroutine second() bind(c, name='hf_step')
| 2~~~~~~~~~~~~~~~~
Error: Procedure 'first' with binding label 'hf_step' at (1) uses the same global identifier as entity at (2)
LLVM Flang 23 reported the same mistake, in its own format:
error: Semantic errors in 11_dup.f90
11_dup.f90:7:14: error: Two entities have the same global name 'hf_step'
subroutine second() bind(c, name='hf_step')
^^^^^^
11_dup.f90:5:14: Conflicting declaration
subroutine first() bind(c, name='hf_step')
^^^^^
Exposing heatflow to C
The heatflow project ships one C-callable function, heatflow_step: it advances an nx by ny temperature field
by one FTCS step and returns a status. It lives in the module heatflow_capi, which wraps the module procedure
ftcs_sweep of heatflow_kernels (the kernel that the solvers of
Modules and Submodules and
Object Oriented Programming also use):
!> C API: heatflow_step advances an nx-by-ny field by one FTCS step.
module heatflow_capi
use, intrinsic :: iso_c_binding, only: c_int, c_double
use heatflow_kernels, only: ftcs_sweep
implicit none
private
public :: heatflow_step
contains
function heatflow_step(nx, ny, u, unew, alpha, dt, dx, dy, residual) &
bind(c, name='heatflow_step') result(status)
integer(c_int), value, intent(in) :: nx, ny
real(c_double), intent(in) :: u(nx, ny)
real(c_double), intent(inout) :: unew(nx, ny)
real(c_double), value, intent(in) :: alpha, dt, dx, dy
real(c_double), intent(out) :: residual
integer(c_int) :: status
if (nx < 3 .or. ny < 3) then
residual = 0.0_c_double
status = 1_c_int
return
end if
call ftcs_sweep(u, unew, alpha, dt, dx, dy, residual)
status = 0_c_int
end function heatflow_step
end module heatflow_capi
Every dummy argument is either value (the sizes and the scalar coefficients) or passed by address (the two fields and
the residual). The field u(nx, ny) is an explicit-shape array whose bounds are the other value dummy arguments:
the bounds of an explicit-shape array may be specified from earlier dummy arguments, and an explicit-shape array is
exactly what a C double * pointing at nx * ny contiguous values corresponds to (18.3.6). The bind(c, name='heatflow_step')
clause is redundant, since the default label is the lower-case Fortran name, but writing the label makes the exported
symbol grep-able. Inside, the code calls the ordinary Fortran kernel, whose assumed-shape arguments receive a normal
Fortran descriptor built by the compiler, so there is nothing C-specific below the interface.
The header is what C and C++ callers include. It is hand-written and has to be kept in step with the Fortran
interface by the author, because no tool checks one against the other. extern "C" is needed only for C++:
#ifndef HEATFLOW_H
#define HEATFLOW_H
#ifdef __cplusplus
extern "C" {
#endif
/* Advance the nx-by-ny column-major field u by one FTCS step into unew.
Returns 0 on success, 1 if the grid is smaller than 3x3. */
int heatflow_step(int nx, int ny, const double *u, double *unew,
double alpha, double dt, double dx, double dy,
double *residual);
#ifdef __cplusplus
}
#endif
#endif
The C client sets one hot cell, calls the function once and prints what came back:
#include <stdio.h>
#include <stdlib.h>
#include "heatflow.h"
int main(void) {
enum { NX = 21, NY = 21 };
double u[NX * NY] = {0}, unew[NX * NY] = {0}, residual = 0.0;
u[(NX / 2) + NX * (NY / 2)] = 100.0;
unew[(NX / 2) + NX * (NY / 2)] = 100.0;
int status = heatflow_step(NX, NY, u, unew, 1.0, 5.0e-5, 0.05, 0.05, &residual);
printf("status=%d residual=%.4f center=%.4f\n", status, residual, unew[(NX / 2) + NX * (NY / 2)]);
return status;
}
The Fortran sources are compiled with the strict flags, put in an archive, and the program is linked with gcc. The Fortran
runtime library is not part of the C link line, so -lgfortran is added. The need is real even though heatflow_step does no
I/O: the strict flags include -fcheck=all, which makes the compiler call the runtime to report errors, and a link
without -lgfortran fails with an undefined reference to _gfortran_runtime_error_at, reported once for every check:
/usr/bin/ld: ./libheatflow_capi.a(heatflow_capi.o): in function `heatflow_step':
/work/02_capi/heatflow_capi.f90:11:(.text+0x50): undefined reference to `_gfortran_runtime_error_at'
Built without -fcheck=all the same archive linked without the runtime library, but a library should not depend on that.
The build with the runtime library is:
gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all -c heatflow_kinds.f90 heatflow_kernels.f90 heatflow_capi.f90
ar rcs libheatflow_capi.a heatflow_kinds.o heatflow_kernels.o heatflow_capi.o
gcc -std=c17 -Wall -Wextra -pedantic -c c_client.c -o c_client.o
gcc c_client.o -L. -lheatflow_capi -lgfortran -lm -o c_client
./c_client
status=0 residual=8.0000 center=92.0000
The residual 8.0 is the largest change of any point in one step (the hot cell, 100, relaxing to 92), and the same call
returned the same two numbers from the Python wrapper of
Calling Fortran from C, C++ and Python.
The same sources built with Flang (flang-23 -c ..., ar, and flang-23 c_client.o -L. -lhf -o c_client_flang, which
lets the Flang driver add its runtime) printed the identical line. A C object that refers to a symbol of the C library, such as
cbrt in the callback program below, could not be linked by the Flang driver unless it was compiled with -fPIC; without
the option the linker stopped with the message below, because Flang links position-independent executables by default on
this platform:
/usr/bin/ld: 07_proc.o: relocation R_AARCH64_ADR_PREL_PG_HI21 against symbol `cbrt@@GLIBC_2.17' which may bind externally can not be used when making a shared object; recompile with -fPIC
The C files of those examples were therefore compiled with -fPIC. The same
library built as a shared library (gfortran -shared) was linked by gcc and by g++, and both programs ran. The header
was also compiled as C++ (g++ -std=c++20 -Wall -Wextra -pedantic -c) without a diagnostic, which checks that the
extern "C" guard works.
Name mangling, and why bind(c) replaced the underscore convention
A Fortran compiler does not export a procedure under the name in the source. What it does with the name is the
mangling scheme of the compiler and is not defined by the standard. nm on the archive shows both what bind(c) gives
and what an ordinary module procedure gets (the address column is removed):
GNU Fortran 16.2:
T __heatflow_kernels_MOD_ftcs_sweep
T __heatflow_kernels_MOD_jacobi_sweep
T heatflow_step
LLVM Flang 23:
T _QMheatflow_kernelsPftcs_sweep
T _QMheatflow_kernelsPjacobi_sweep
T heatflow_step
The two compilers disagree about every name except the one with bind(c): the module procedures are encoded with the
module name, in different formats. That is why a C file cannot reasonably call a module procedure without bind(c), and
why a library mixing compilers must expose only bind(c) names.
Before Fortran 2003 the only way to call Fortran from C was to guess the symbol. The common convention of Unix compilers
was the lower-case name followed by one trailing underscore, with every argument passed by reference and, for each
character argument, a hidden length argument appended at the end of the argument list. Code written for it still exists in large
numerical libraries (BLAS and LAPACK among them, see
BLAS, LAPACK and Numerical Libraries),
and is shown here only to explain what bind(c) removed. The external subroutine below has no bind(c):
! Not interoperable: no bind(c). The linker name is chosen by the compiler.
subroutine scale_field(n, u, factor)
implicit none
integer, intent(in) :: n
double precision, intent(inout) :: u(n)
double precision, intent(in) :: factor
u = u*factor
end subroutine scale_field
#include <stdio.h>
/* Old convention: lower-case name plus one trailing underscore, every argument by pointer. */
void scale_field_(const int *n, double *u, const double *factor);
int main(void) {
double u[3] = {1.0, 2.0, 3.0};
int n = 3;
double factor = 2.5;
scale_field_(&n, u, &factor);
printf("%.1f %.1f %.1f\n", u[0], u[1], u[2]);
return 0;
}
Both compilers exported the symbol scale_field_ (checked with nm), the C program above linked against it and printed
2.5 5.0 7.5 with GNU Fortran and with Flang. The option names that change the convention exist, with these effects on the
same object file:
gfortran
0000000000000000 T scale_field_
gfortran -fno-underscoring
0000000000000000 T scale_field
gfortran -fsecond-underscore
0000000000000000 T scale_field__
flang -fno-underscoring
0000000000000000 T scale_field
The first gfortran line, with no option, is the default scale_field_. -fno-underscoring removes the underscore,
-fsecond-underscore appends a second one to a name that already contains an underscore (an option for compatibility
with older compilers; the GNU Fortran manual documents it with the code generation options), and Flang has -funderscoring and
-fno-underscoring, which gave the same result as GNU Fortran in the last line; whether a given library was built with one of them is
a property of that build, which is why CMake ships a FortranCInterface module (see
Building with Make, CMake and Meson) to detect
the convention. A caller of old code must also know that an argument by reference is the default, and that a character
argument adds a hidden length argument which is passed by the compiler, not described by the name; a mistake in either
corrupts the stack or the data silently. New code uses bind(c): the symbol is the binding label on every compiler and
platform, the argument passing is stated in the interface (value or by address), and a string is a plain char *.
Arrays: column-major, explicit-shape and assumed-size
A Fortran array stores its first index fastest; a C array stores its last index fastest. An interoperable Fortran array
argument is an explicit-shape or assumed-size array (18.3.6), and its dimension list reads in the opposite order to
the C declaration: Fortran a(18, 3:7, *) interoperates with C int b[][5][18] (18.3.6, Note 3). The last (slowest) Fortran dimension corresponds to the
first (outermost) C dimension, which is the one that may be * in Fortran and empty in C. A pointer to the first element serves for
the whole array, and the shape is communicated by other arguments, which is the contract of heatflow_step. The next
program makes the mapping visible: C fills a[i][j] with 10*i + j for 2 rows and 3 columns, and the Fortran declaration is
a(3, 2).
/* A C array of 2 rows by 3 columns: a[i][j] with j varying fastest in memory. */
void hf_fill_rows(double a[2][3]) {
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 3; ++j) a[i][j] = 10.0 * i + j;
}
program c_row_major
use, intrinsic :: iso_c_binding, only: c_double
implicit none
interface
subroutine hf_fill_rows(a) bind(c) ! void hf_fill_rows(double a[2][3])
import :: c_double
real(c_double), intent(out) :: a(3, 2) ! the dimensions are reversed: C a[i][j] is Fortran a(j+1, i+1)
end subroutine hf_fill_rows
end interface
real(c_double) :: a(3, 2)
integer :: j
call hf_fill_rows(a)
do j = 1, 3
print '(a, i0, a, 2f6.1)', 'a(', j, ', 1:2) = ', a(j, :)
end do
print '(a, f4.1)', 'C a[1][2] is Fortran a(3, 2) = ', a(3, 2)
end program c_row_major
a(1, 1:2) = 0.0 10.0
a(2, 1:2) = 1.0 11.0
a(3, 1:2) = 2.0 12.0
C a[1][2] is Fortran a(3, 2) = 12.0
C element a[1][2] is Fortran a(3, 2): the C row index becomes the last Fortran index, and the C column index the
first. There is nothing to transpose in memory; both languages are looking at the same bytes, and a loop that wants
unit stride runs over the first Fortran index and the last C index. For a two-dimensional field such as heatflow's
u(nx, ny) the C code reads u[j * nx + i] (or declares double u[ny][nx]), which is what c_client.c does with
u[(NX / 2) + NX * (NY / 2)]. The layout rules for Fortran arrays are in
Arrays Fundamentals.
Four more facts about arrays at the boundary:
-
An array section that is not contiguous, such as
u(1:10:2), is passed to an explicit-shape or assumed-size dummy argument through a contiguous temporary that the compiler creates and copies back after the call forintent(out)andintent(inout). C then sees contiguous memory, and a C routine that keeps the pointer after the call keeps a pointer to a dead temporary. -
An allocatable array or a pointer array is never an interoperable variable (18.3.6, Note 2). Pass the first element (
u(1, 1)), the section, orc_loc(u), or declare the dummy argument allocatable, assumed-shape or assumed-rank and let the C side deal with a descriptor, as in C Descriptors and ISO_Fortran_binding. -
An assumed-size dummy
x(*)has no bounds; C cannot ask Fortran for the size, so a count travels in another argument. Bounds checking (-fcheck=bounds) cannot catch an overrun made inside the C function. -
c_locdoes not accept a zero-sized array (18.2.3.7), and a C function must not dereference the address that a zero-sized actual argument produces.
Derived types and enumerations
A Fortran derived type is interoperable with a C struct if it has the bind(c) attribute, has the same number of
components, and the components in the same positions interoperate (18.3.4). The names of the type and of the components do
not matter. Constraints C1801-C1806 forbid sequence, type parameters, extends, type-bound procedures, an
empty component list, and any pointer or allocatable component. A bit field, a flexible array member and a union have no
Fortran counterpart. A C void * member becomes type(c_ptr), which is how an allocatable, a pointer or a procedure
reaches a structure. The details of the layout rules, including the padding that both languages insert, are in
Derived Types; the next program uses a structure with an array
member, a character array member and a pointer member, filled by C and read in Fortran, together with a C enumeration:
#include <stddef.h>
typedef struct {
int id;
double position[2];
char label[8];
void *user_data;
} hf_probe;
enum hf_bc { HF_DIRICHLET = 1, HF_NEUMANN = 2, HF_PERIODIC = 3 };
/* Fill the probe array: ids 1..n, positions along the x axis, labels "P1".. */
void hf_make_probes(hf_probe *p, size_t n) {
for (size_t i = 0; i < n; ++i) {
p[i].id = (int)i + 1;
p[i].position[0] = 0.25 * (double)i;
p[i].position[1] = 0.5;
p[i].label[0] = 'P';
p[i].label[1] = (char)('1' + i);
p[i].label[2] = '\0';
p[i].user_data = NULL;
}
}
/* Name of a boundary condition as an enum value. */
const char *hf_bc_name(enum hf_bc bc) {
switch (bc) {
case HF_DIRICHLET: return "dirichlet";
case HF_NEUMANN: return "neumann";
case HF_PERIODIC: return "periodic";
}
return "unknown";
}
size_t hf_probe_size(void) { return sizeof(hf_probe); }
program c_types
use, intrinsic :: iso_c_binding, only: c_int, c_double, c_char, c_ptr, c_size_t, c_null_char, &
c_sizeof, c_associated, c_f_pointer
implicit none
type, bind(c) :: hf_probe ! typedef struct { ... } hf_probe;
integer(c_int) :: id
real(c_double) :: position(2)
character(kind=c_char) :: label(8) ! char label[8]
type(c_ptr) :: user_data ! void *user_data
end type hf_probe
enum, bind(c) ! enum hf_bc { HF_DIRICHLET = 1, ... }
enumerator :: hf_dirichlet = 1, hf_neumann = 2, hf_periodic = 3
end enum
interface
subroutine hf_make_probes(p, n) bind(c)
import :: hf_probe, c_size_t
type(hf_probe), intent(out) :: p(*) ! hf_probe *p
integer(c_size_t), value, intent(in) :: n
end subroutine hf_make_probes
function hf_bc_name(bc) bind(c) result(name)
import :: c_int, c_ptr
integer(c_int), value, intent(in) :: bc ! the enum is passed as an int
type(c_ptr) :: name ! const char *
end function hf_bc_name
function hf_probe_size() bind(c) result(n)
import :: c_size_t
integer(c_size_t) :: n
end function hf_probe_size
end interface
type(hf_probe) :: probes(3)
character(kind=c_char), pointer :: text(:)
type(c_ptr) :: p
integer :: i, k
call hf_make_probes(probes, 3_c_size_t)
print '(a, i0, a, i0)', 'sizeof in C = ', hf_probe_size(), ', c_sizeof in Fortran = ', c_sizeof(probes(1))
do i = 1, size(probes)
k = findloc(probes(i)%label, c_null_char, 1) - 1 ! characters before the terminator
print '(a, i0, a, 2f5.2, 3a, l1)', 'probe ', probes(i)%id, ' at', probes(i)%position, &
' label=', transfer(probes(i)%label(1:k), repeat(' ', k)), ' user_data associated=', c_associated(probes(i)%user_data)
end do
print '(a, 3i2)', 'enumerators: ', hf_dirichlet, hf_neumann, hf_periodic
do i = hf_dirichlet, hf_periodic ! enumerators are integer(c_int) constants
p = hf_bc_name(i)
call c_f_pointer(p, text, [16]) ! view 16 characters; the string ends at the first null
k = findloc(text, c_null_char, 1) - 1
print '(a, i0, 2a)', 'bc ', i, ' is ', transfer(text(1:k), repeat(' ', k))
end do
end program c_types
sizeof in C = 40, c_sizeof in Fortran = 40
probe 1 at 0.00 0.50 label=P1 user_data associated=F
probe 2 at 0.25 0.50 label=P2 user_data associated=F
probe 3 at 0.50 0.50 label=P3 user_data associated=F
enumerators: 1 2 3
bc 1 is dirichlet
bc 2 is neumann
bc 3 is periodic
Flang 23 gave identical output. c_sizeof(probes(1)) matches sizeof(hf_probe), 40 bytes: 4 (id), 4 padding, 16 (position),
8 (label) and 8 (user_data). If the two disagree, a component has a different kind or the compilers pad differently, and
data is corrupted silently; printing both sizes in a unit test is an inexpensive guard. The array of hf_probe is
passed as an assumed-size array, type(hf_probe), intent(out) with the dimension (*), the Fortran way of writing hf_probe *p.
An enumeration made interoperable with enum, bind(c) (7.6.1) has the kind of the C integer type chosen for the
corresponding C enum and enumerators that are named constants of that kind; here the three enumerators are
integer(c_int) and pass to a value dummy argument of kind c_int. 18.3.3 says that an enum type interoperates with its
C enumerated type and with the C integer type that interoperates with its enumerators. The heatflow module
heatflow_boundary declares its boundary conditions this way (dirichlet = 1, neumann = 2, periodic = 3), which is why
those values can be handed to C code without a table. The Fortran 2023 enumeration type is a different feature and is
not interoperable; see Enumerations.
A string member such as label[8] is character(kind=c_char) with dimension(8), an array of one-character elements, not a
character(len=8) scalar. The terminating null character is part of the array, and the Fortran code above searches for it
with findloc(probes(i)%label, c_null_char, 1).
Global variables
A C variable with external linkage can be a module variable in Fortran, and the other way round (18.9). The Fortran variable
needs bind(c), which can be given with name= as in the example, and must be declared in the specification part of a
module (C819) or be a common block. At most one of the two sides defines the variable: a C file defines int hf_verbosity = 2;
and the Fortran module only refers to it, whereas hf_steps_done is defined (and so allocated) by the Fortran module, and
declared extern int hf_steps_done; in C. A variable cannot be initialised by both sides (18.9.1).
int hf_verbosity = 2; /* defined in C, used from Fortran */
double hf_scale = 0.5; /* defined in C, used from Fortran under another name */
extern int hf_steps_done; /* defined in Fortran, used from C */
int hf_report(void) { return hf_steps_done * hf_verbosity; }
module heatflow_globals
use, intrinsic :: iso_c_binding, only: c_int, c_double
implicit none
private
public :: verbosity, scale, steps_done
integer(c_int), bind(c, name='hf_verbosity') :: verbosity ! the C variable hf_verbosity
real(c_double), bind(c, name='hf_scale') :: scale ! the C variable hf_scale
integer(c_int), bind(c, name='hf_steps_done') :: steps_done ! defined here; C refers to it
end module heatflow_globals
program c_globals
use, intrinsic :: iso_c_binding, only: c_int
use heatflow_globals, only: verbosity, scale, steps_done
implicit none
interface
function hf_report() bind(c) result(r)
import :: c_int
integer(c_int) :: r
end function hf_report
end interface
print '(a, i0, a, f4.2)', 'values set in C: verbosity=', verbosity, ' scale=', scale
steps_done = 21
verbosity = 3
print '(a, i0)', 'C function computes steps_done*verbosity = ', hf_report()
end program c_globals
values set in C: verbosity=2 scale=0.50
C function computes steps_done*verbosity = 63
Flang 23 printed the same two lines. The binding label of a variable follows the same rule as the one of a procedure
(18.9.2): case-sensitive when given with name=, the lower-case Fortran name otherwise. Module variables with bind(c)
are shared state for the whole program, with all the drawbacks of global state; use them for flags and constants the C
library owns (a verbosity level, an error code), and pass anything else as arguments.
C pointers and function pointers
type(c_ptr) and type(c_funptr) are derived types with private components that are interoperable with any C object
pointer and any C function pointer (18.3.2). They are opaque in Fortran: there is no arithmetic, no dereference and no
comparison with ==. They get their values from C, from c_loc and c_funloc, or from the constants c_null_ptr and
c_null_funptr, and are turned back into Fortran pointers with c_f_pointer and c_f_procpointer. A type(c_ptr) with
the value attribute is the Fortran spelling of void *.
| Procedure | What it does | Notes |
|---|---|---|
|
the C address of |
|
|
the C address of a procedure, a |
a C function may call the result only if the procedure is interoperable (18.10.1) |
|
associates the Fortran pointer |
|
|
associates a Fortran procedure pointer with a C function pointer |
the interface of |
|
true if |
works on |
|
the size of |
the same as C |
All the procedures of the module are generic. c_f_pointer, c_f_procpointer and c_f_strpointer are impure subroutines; the
others (c_associated, c_funloc, c_loc, c_sizeof and f_c_string) are simple (18.2.3.1), which is what allows them in
pure procedures and do concurrent.
The next program makes the C library own an array: hf_alloc_field returns a malloced array of doubles, Fortran views it as
a rank-one array pointer (with default and with zero-based bounds, using lower), a Fortran allocatable array is zeroed by a
C routine that takes only an address and a byte count, and the memory is returned to its owner at the end:
#include <stdlib.h>
/* The C library owns this memory: n doubles, initialised to 20.0. */
double *hf_alloc_field(size_t n) {
double *p = malloc(n * sizeof *p);
if (p != NULL) {
for (size_t i = 0; i < n; ++i) p[i] = 20.0;
}
return p;
}
void hf_free_field(void *p) { free(p); }
/* Generic buffer API: only an address and a byte count. */
void hf_zero_bytes(void *buf, size_t nbytes) {
unsigned char *b = buf;
for (size_t i = 0; i < nbytes; ++i) b[i] = 0;
}
program c_pointers
use, intrinsic :: iso_c_binding, only: c_double, c_ptr, c_size_t, c_loc, c_f_pointer, c_associated, c_sizeof
implicit none
interface
function hf_alloc_field(n) bind(c) result(p)
import :: c_size_t, c_ptr
integer(c_size_t), value, intent(in) :: n
type(c_ptr) :: p ! double *
end function hf_alloc_field
subroutine hf_free_field(p) bind(c)
import :: c_ptr
type(c_ptr), value, intent(in) :: p ! void *
end subroutine hf_free_field
subroutine hf_zero_bytes(buf, nbytes) bind(c)
import :: c_ptr, c_size_t
type(c_ptr), value, intent(in) :: buf ! void *
integer(c_size_t), value, intent(in) :: nbytes
end subroutine hf_zero_bytes
end interface
type(c_ptr) :: raw
real(c_double), pointer :: field(:), shifted(:)
real(c_double), allocatable, target :: local(:)
raw = hf_alloc_field(5_c_size_t)
if (.not. c_associated(raw)) error stop 'allocation failed in C'
call c_f_pointer(raw, field, [5]) ! indices 1:5
call c_f_pointer(raw, shifted, [5], [0]) ! Fortran 2023 lower=: indices 0:4 over the same memory
field(2) = 99.0_c_double
print '(a, 5f6.1)', 'field (1:5) = ', field
print '(a, i0, a, i0, a, f5.1)', 'shifted bounds ', lbound(shifted, 1), ':', ubound(shifted, 1), ', shifted(1) = ', shifted(1)
allocate (local(4), source=7.0_c_double) ! c_loc accepts an allocated allocatable (Fortran 2008)
call hf_zero_bytes(c_loc(local), c_sizeof(local))
print '(a, 4f4.1)', 'zeroed by C = ', local
print '(a, l1)', 'c_associated(c_loc(local(1)), c_loc(local)) = ', c_associated(c_loc(local(1)), c_loc(local))
nullify (field, shifted) ! drop the Fortran views before the owner frees the memory
call hf_free_field(raw)
end program c_pointers
field (1:5) = 20.0 99.0 20.0 20.0 20.0
shifted bounds 0:4, shifted(1) = 99.0
zeroed by C = 0.0 0.0 0.0 0.0
c_associated(c_loc(local(1)), c_loc(local)) = T
Flang 23 printed the same four lines. The call c_f_pointer(raw, shifted, [5], [0]) is the Fortran 2023 form; GNU Fortran
16.2 and Flang 23 both accepted it in this strict build. Before it, a pointer was always indexed from 1 and the usual
workaround was a pointer remapping, shifted(0:4) => field, which does the same in any edition and is still valid.
The allocatable local is passed through c_loc(local): a c_loc of an allocated allocatable variable is allowed (the
note to 18.3.2 says it can be used where the allocatable could not be passed directly), and its argument needs the target attribute.
The rules a program must follow with these pointers are not enforced by the compiler:
-
Lifetime. A C pointer to part of a Fortran object becomes undefined under the same conditions as a Fortran pointer to it would (18.8). If the target is a local variable, an automatic array or an allocatable that is deallocated, the address is dangling after return or deallocation; and if a Fortran dummy argument has neither
targetnorasynchronous, every C pointer into the object it was associated with becomes undefined when the call returns. Thetargetattribute is also what stops the compiler from assuming that nothing else can alias the variable. -
Ownership. Whoever allocated frees: memory from C’s
mallocis released withfree, never with Fortran’sdeallocate, and memory from Fortran’sallocatenever withfree. The program above nullifies the Fortran pointers before the owner frees, sincefptrhas no way to know. -
Alignment and type.
c_f_pointertrusts the address; the type offptrhas to match the data, and the standard (18.2.3.3) states that the target must be the C address of an interoperable entity, ac_locof a noninteroperable one, or storage not in use by any other Fortran entity. -
Allocatable and pointer arrays. They are not interoperable variables; to hand one to a C function that wants a plain pointer, pass
c_loc(a)(ora(1)) of an allocated, contiguous, non-empty array, as the program does withlocal.
Callbacks: c_funloc and c_f_procpointer
Function pointers work in both directions. The first program gives qsort from the C library a Fortran comparison function:
the callback is an ordinary bind(c) Fortran function, its two arguments are void * (type(c_ptr), value) and are turned
into Fortran pointers with c_f_pointer, and c_funloc gives qsort its address. c_sizeof supplies the element size.
module heatflow_sort
use, intrinsic :: iso_c_binding, only: c_int, c_double, c_ptr, c_funptr, c_size_t, c_f_pointer
implicit none
private
public :: c_qsort, compare_ascending
interface
! void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
subroutine c_qsort(base, nmemb, size, compar) bind(c, name='qsort')
import :: c_ptr, c_size_t, c_funptr
type(c_ptr), value, intent(in) :: base
integer(c_size_t), value, intent(in) :: nmemb, size
type(c_funptr), value, intent(in) :: compar
end subroutine c_qsort
end interface
contains
! The callback must itself be interoperable: bind(c), value or pointer arguments, C kinds.
function compare_ascending(a, b) bind(c) result(order)
type(c_ptr), value, intent(in) :: a, b
integer(c_int) :: order
real(c_double), pointer :: x, y
call c_f_pointer(a, x)
call c_f_pointer(b, y)
order = merge(-1_c_int, merge(1_c_int, 0_c_int, x > y), x < y)
end function compare_ascending
end module heatflow_sort
program c_qsort_demo
use, intrinsic :: iso_c_binding, only: c_double, c_size_t, c_loc, c_funloc, c_sizeof
use heatflow_sort, only: c_qsort, compare_ascending
implicit none
real(c_double), target :: temperature(6) = [30.5_c_double, 12.0_c_double, 99.0_c_double, 0.5_c_double, 42.0_c_double, 7.25_c_double]
call c_qsort(c_loc(temperature), size(temperature, kind=c_size_t), c_sizeof(temperature(1)), c_funloc(compare_ascending))
print '(a, 6f7.2)', 'sorted by libc qsort: ', temperature
end program c_qsort_demo
sorted by libc qsort: 0.50 7.25 12.00 30.50 42.00 99.00
Flang 23 printed the same line. The second program goes the other way: a C function returns a function pointer chosen
at run time, c_f_procpointer turns it into a Fortran procedure pointer whose interface is the abstract interface
unary_fn (an abstract interface with bind(c), the analogue of a typedef for the C pointer type), and the same raw
c_funptr is also given back to C to be called on every element of an array:
#include <math.h>
typedef double (*hf_fn)(double);
static double square(double x) { return x * x; }
/* Returns a C function pointer chosen at run time. */
hf_fn hf_pick(int which) { return which == 0 ? square : cbrt; }
/* Calls back a function pointer on every element. */
void hf_apply(double *x, int n, hf_fn f) {
for (int i = 0; i < n; ++i) x[i] = f(x[i]);
}
program c_procedure_pointers
use, intrinsic :: iso_c_binding, only: c_int, c_double, c_funptr, c_funloc, c_f_procpointer, c_associated
implicit none
abstract interface
function unary_fn(x) bind(c) result(y) ! double (*)(double)
import :: c_double
real(c_double), value, intent(in) :: x
real(c_double) :: y
end function unary_fn
end interface
interface
function hf_pick(which) bind(c) result(f)
import :: c_int, c_funptr
integer(c_int), value, intent(in) :: which
type(c_funptr) :: f
end function hf_pick
subroutine hf_apply(x, n, f) bind(c)
import :: c_double, c_int, c_funptr
real(c_double), intent(inout) :: x(*)
integer(c_int), value, intent(in) :: n
type(c_funptr), value, intent(in) :: f
end subroutine hf_apply
end interface
procedure(unary_fn), pointer :: fn
real(c_double) :: values(3) = [1.0_c_double, 8.0_c_double, 27.0_c_double]
type(c_funptr) :: raw
raw = hf_pick(1_c_int)
if (.not. c_associated(raw)) error stop 'no function'
call c_f_procpointer(raw, fn) ! a Fortran procedure pointer to the C function cbrt
print '(a, f4.1)', 'cbrt(27) through a procedure pointer = ', fn(27.0_c_double)
call hf_apply(values, size(values, kind=c_int), raw) ! C calls cbrt on each element
print '(a, 3f4.1)', 'hf_apply with cbrt = ', values
call hf_apply(values, size(values, kind=c_int), hf_pick(0_c_int))
print '(a, 3f4.1)', 'hf_apply with square = ', values
end program c_procedure_pointers
cbrt(27) through a procedure pointer = 3.0
hf_apply with cbrt = 1.0 2.0 3.0
hf_apply with square = 1.0 4.0 9.0
Again the output of Flang 23 was identical. A callback has to be interoperable (bind(c), interoperable dummy arguments);
c_funloc can also be applied to a procedure that is not interoperable, but then a C function must not call the
address (18.10.1), the result being usable only with c_f_procpointer on the Fortran side, and the standard recommends that
such a pointer be declared void (*)() in C. A procedure defined by Fortran must not be invoked as a signal handler and must
not invoke setjmp or longjmp; and if a procedure defined by other means calls setjmp or longjmp, it must not cause any
Fortran procedure to be invoked (18.10.1).
Strings across the boundary
A C string is a char array ended by a null character, and a Fortran string is a character value with a length and no
terminator. The two meet in three situations: passing a Fortran string to C, filling a Fortran buffer from C, and viewing a C
string that C owns. Each has an idiom, and the Fortran 2023 procedures remove the boilerplate of the first and the last.
-
Fortran to C.
trim(s) // c_null_charbuilds a C string by hand. Fortran 2023 addsf_c_string(s [, asis])(18.2.3.9): the result has the characters ofs(without trailing blanks unlessasisis true) followed byc_null_char. Either value can be passed straight to a dummy argumentcharacter(kind=c_char)withdimension(*), because a character scalar actual argument may be associated with an array dummy argument by sequence association (15.5.2.12, and the note at the end of 18.3.7). -
C fills a Fortran buffer. The buffer is an array of
character(kind=c_char), and the code converts it to a character variable of the right length by looping over the characters up to the null, or byfindlocas before. -
C owns the string. The function returns a
c_ptr. Find the length withstrlen(a one-line interface to the C library), thenc_f_pointer(p, chars, [n])gives an array ofncharacters, or Fortran 2023c_f_strpointergives a deferred-lengthcharacter(len=:), pointerthat can be used as a string.
#include <stdio.h>
#include <string.h>
/* Writes a C string into the caller's buffer; returns the length without the null. */
size_t hf_version(char *out, size_t capacity) {
int n = snprintf(out, capacity, "heatflow %d.%d", 0, 1);
return n < 0 ? 0 : (size_t)n;
}
/* Counts the characters of the file extension. */
size_t hf_extension_length(const char *name) {
const char *dot = strrchr(name, '.');
return dot == NULL ? 0 : strlen(dot + 1);
}
program c_strings
use, intrinsic :: iso_c_binding, only: c_char, c_null_char, c_ptr, c_size_t, c_f_pointer, c_associated, f_c_string
implicit none
interface
function hf_version(out, capacity) bind(c) result(n)
import :: c_char, c_size_t
character(kind=c_char), intent(out) :: out(*) ! char *: an array of c_char, not a character(len=*)
integer(c_size_t), value, intent(in) :: capacity
integer(c_size_t) :: n
end function hf_version
function hf_extension_length(name) bind(c) result(n)
import :: c_char, c_size_t
character(kind=c_char), intent(in) :: name(*) ! const char *
integer(c_size_t) :: n
end function hf_extension_length
function c_getenv(name) bind(c, name='getenv') result(p)
import :: c_char, c_ptr
character(kind=c_char), intent(in) :: name(*)
type(c_ptr) :: p
end function c_getenv
function c_strlen(s) bind(c, name='strlen') result(n)
import :: c_ptr, c_size_t
type(c_ptr), value, intent(in) :: s
integer(c_size_t) :: n
end function c_strlen
end interface
character(kind=c_char) :: buffer(32)
character(len=:), allocatable :: text
type(c_ptr) :: p
character(kind=c_char), pointer :: chars(:)
integer(c_size_t) :: n
! C writes into a Fortran buffer; the Fortran side converts n characters to a string
n = hf_version(buffer, size(buffer, kind=c_size_t))
text = to_string(buffer(1:n))
print '(3a, i0)', 'hf_version returned "', text, '", length ', n
! A Fortran string needs its terminator; a scalar actual argument may correspond to an array dummy
print '(a, i0)', 'extension length (trim // c_null_char): ', hf_extension_length(trim('heatflow.csv ') // c_null_char)
print '(a, i0)', 'extension length (f_c_string): ', hf_extension_length(f_c_string('heatflow.nc '))
! A string owned by C comes back as a c_ptr; strlen tells how many characters to view
p = c_getenv(f_c_string('HEATFLOW_OUT'))
if (c_associated(p)) then
call c_f_pointer(p, chars, [c_strlen(p)])
print '(3a)', 'HEATFLOW_OUT = ', to_string(chars), '.'
else
print '(a)', 'HEATFLOW_OUT is not set'
end if
contains
! An array of single characters is not a string: print it with 'a' and every element lands on its own line.
pure function to_string(chars) result(text)
character(kind=c_char), intent(in) :: chars(:)
character(len=size(chars)) :: text
integer :: i
do i = 1, size(chars)
text(i:i) = chars(i)
end do
end function to_string
end program c_strings
The program was run with HEATFLOW_OUT=results/heat.csv in the environment (the name is invented), once with GNU Fortran 16.2 and
once with Flang 23, and printed the same lines:
hf_version returned "heatflow 0.1", length 12
extension length (trim // c_null_char): 3
extension length (f_c_string): 2
HEATFLOW_OUT = results/heat.csv.
With the variable unset, the branch of the if that reports the null pointer was taken and printed
HEATFLOW_OUT is not set (getenv returns a null pointer, which c_associated detects). Two details of the program
are lessons: the extension length is 3 for 'heatflow.csv ' with trim, and 2 for 'heatflow.nc ' with f_c_string, that is,
f_c_string really did remove the trailing blanks; and a function to_string was needed because print '(3a)' of an
array of single characters writes one element per format repetition, putting every character on its own line. That is
an easy mistake to make with c_char arrays, and transfer(chars, repeat(' ', size(chars))) is the one-line
alternative. f_c_string is available in both compilers, so the program above, which uses it for the name argument of getenv, ran
unchanged with both.
Fortran 2023 c_f_strpointer is accepted by LLVM Flang 23 and is not provided by GNU Fortran 16.2. The program below uses
it with strlen for the length:
! Fortran 2023 C_F_STRPOINTER (18.2.3.5). GNU Fortran 16.2 does not provide it; LLVM Flang 23 does.
program c_strpointer_getenv
use, intrinsic :: iso_c_binding, only: c_char, c_ptr, c_size_t, c_associated, c_f_strpointer, f_c_string
implicit none
interface
function c_getenv(name) bind(c, name='getenv') result(p)
import :: c_char, c_ptr
character(kind=c_char), intent(in) :: name(*)
type(c_ptr) :: p
end function c_getenv
function c_strlen(s) bind(c, name='strlen') result(n)
import :: c_ptr, c_size_t
type(c_ptr), value, intent(in) :: s
integer(c_size_t) :: n
end function c_strlen
end interface
character(kind=c_char, len=:), pointer :: value
type(c_ptr) :: p
p = c_getenv(f_c_string('HEATFLOW_OUT'))
if (c_associated(p)) then
call c_f_strpointer(p, value, int(c_strlen(p))) ! NCHARS = the exact length, found with strlen
print '(a, i0, 3a)', 'length ', len(value), ' value "', value, '"'
else
print '(a)', 'HEATFLOW_OUT is not set'
end if
end program c_strpointer_getenv
LLVM Flang 23 printed:
length 16 value "results/heat.csv"
GNU Fortran 16.2 stopped at the use statement:
08_strpointer.f90:3:79:
3 | use, intrinsic :: iso_c_binding, only: c_char, c_ptr, c_size_t, c_associated, c_f_strpointer, f_c_string
| 1
Error: Symbol 'c_f_strpointer' referenced at (1) not found in intrinsic module ISO_C_BINDING
The standard says that the length of the pointer becomes the largest value, not above nchars, for which no null
character is in the sequence (18.2.3.5), so that nchars is an upper bound and the usual call c_f_strpointer(p, value,
1023) is meant to work for any string of up to 1023 characters; that is the form the standard’s own getenv example uses.
In Flang 23 the length was exactly nchars in these experiments (a call with nchars = 17 for the 16-character string
gave a length-17 value ending with the null character, and one with 64 or 1023 crashed with a segmentation fault when the
value was printed), so on that compiler the safe use passes the exact length, as the program does. The exact-length call is correct under both readings of the text. The
portable code today is strlen and c_f_pointer, as in the previous program.
Strings can also be passed in a bind(c) interface as character(kind=c_char, len=*) (assumed length, a Fortran
2018 feature from TS 29113): C then receives a pointer to a descriptor, not a char *, so the C function has to be written
against ISO_Fortran_binding.h. Prefer character(kind=c_char) with dimension(*) for plain C strings. The character handling of
the language itself is in Characters and Strings.
Optional arguments and assumed type
Since Fortran 2018 an interoperable procedure can have optional dummy arguments (18.3.7). When Fortran calls a C function
and the actual argument is absent, C receives a null pointer; when C calls a Fortran procedure, the argument is absent exactly
when C passes a null pointer. An optional dummy argument cannot also have value (C869), since a value has no null.
A type(*) (assumed-type) dummy argument accepts an actual argument of any type and kind, with no type information:
the Fortran way of writing const void *. With dimension(*) it receives the address of the first element of an array
of any type, and c_sizeof of the actual array provides the byte count:
#include <stddef.h>
/* XOR checksum of nbytes bytes, any element type. */
unsigned int hf_checksum(const void *buf, size_t nbytes) {
const unsigned char *b = buf;
unsigned int sum = 0;
for (size_t i = 0; i < nbytes; ++i) sum ^= (unsigned int)b[i] << (8 * (i % 4));
return sum;
}
/* Scales x in place by factor, or by 2 when factor is a null pointer. */
void hf_scale(double *x, int n, const double *factor) {
double f = factor == NULL ? 2.0 : *factor;
for (int i = 0; i < n; ++i) x[i] *= f;
}
program c_assumed_type
use, intrinsic :: iso_c_binding, only: c_int, c_double, c_size_t, c_sizeof
implicit none
interface
! const void *buf: a type(*) dummy accepts an actual argument of any type
function hf_checksum(buf, nbytes) bind(c) result(sum)
import :: c_size_t, c_int
type(*), intent(in) :: buf(*)
integer(c_size_t), value, intent(in) :: nbytes
integer(c_int) :: sum
end function hf_checksum
! const double *factor may be NULL: optional, and absent in Fortran means NULL in C (18.3.7)
subroutine hf_scale(x, n, factor) bind(c)
import :: c_double, c_int
real(c_double), intent(inout) :: x(*)
integer(c_int), value, intent(in) :: n
real(c_double), intent(in), optional :: factor
end subroutine hf_scale
end interface
real(c_double) :: u(4) = [1.0_c_double, 2.0_c_double, 3.0_c_double, 4.0_c_double]
integer(c_int) :: k(3) = [1_c_int, 2_c_int, 4_c_int]
print '(a, z8.8)', 'checksum of 4 doubles: ', hf_checksum(u, c_sizeof(u))
print '(a, z8.8)', 'checksum of 3 ints: ', hf_checksum(k, c_sizeof(k))
call hf_scale(u, size(u), 10.0_c_double)
print '(a, 4f6.1)', 'factor given: ', u
call hf_scale(u, size(u))
print '(a, 4f6.1)', 'factor absent: ', u
end program c_assumed_type
checksum of 4 doubles: 7FE80000
checksum of 3 ints: 00000007
factor given: 10.0 20.0 30.0 40.0
factor absent: 20.0 40.0 60.0 80.0
Flang 23 printed the same four lines. The same function accepted an array of doubles and an array of integers through
type(*), which would otherwise need a void * and c_loc (and the target attribute) or two generic specifics. An
assumed-type dummy argument cannot be allocatable, pointer, value or intent(out), and its name can appear only as
an actual argument for another assumed-type dummy argument, as the first argument of is_contiguous, lbound, present,
rank, shape, size or ubound, or of c_loc (7.3.2.2, C714 and C715); its elements cannot be read in Fortran. That is
why hf_checksum is given the byte count by its caller. Assumed-type, assumed-rank buffers are also how the MPI
bindings accept arrays of any type (see
MPI with mpi_f08).
Fortran calling back into C, and the order of output
The example below is the pattern behind most real C-to-Fortran libraries: a C main calls a Fortran routine for the heavy
work, hands it a function pointer to report progress and an opaque void * that the Fortran code passes back unchanged
(the context pointer, which lets the C callback find its state without a global variable). The Fortran routine is the
Jacobi iteration of heatflow with the kernel of heatflow_kernels; its tolerance is an optional argument (a null pointer
in C) and its callback may be null too.
module heatflow_run
use, intrinsic :: iso_c_binding, only: c_int, c_double, c_ptr, c_funptr, c_f_procpointer, c_associated
use heatflow_kernels, only: jacobi_sweep
implicit none
private
public :: hf_run
abstract interface
! void (*report)(int step, double residual, void *context)
subroutine report_cb(step, residual, context) bind(c)
import :: c_int, c_double, c_ptr
integer(c_int), value, intent(in) :: step
real(c_double), value, intent(in) :: residual
type(c_ptr), value, intent(in) :: context
end subroutine report_cb
end interface
contains
! int hf_run(int nx, int ny, int max_steps, const double *tolerance, void (*report)(...), void *context);
function hf_run(nx, ny, max_steps, tolerance, report, context) bind(c, name='hf_run') result(steps)
integer(c_int), value, intent(in) :: nx, ny, max_steps
real(c_double), intent(in), optional :: tolerance ! optional: a null pointer from C means "absent"
type(c_funptr), value, intent(in) :: report ! may be NULL
type(c_ptr), value, intent(in) :: context ! handed back untouched
integer(c_int) :: steps
real(c_double), allocatable :: u(:, :), unew(:, :)
real(c_double) :: residual, tol
procedure(report_cb), pointer :: notify
tol = 1.0e-3_c_double
if (present(tolerance)) tol = tolerance
notify => null()
if (c_associated(report)) call c_f_procpointer(report, notify)
allocate (u(nx, ny), source=0.0_c_double)
u(1, :) = 100.0_c_double ! hot left edge, fixed
unew = u
steps = 0_c_int
do while (steps < max_steps)
call jacobi_sweep(u, unew, residual)
u = unew
steps = steps + 1_c_int
if (associated(notify) .and. mod(steps, 50_c_int) == 0_c_int) call notify(steps, residual, context)
if (residual < tol) exit
end do
end function hf_run
end module heatflow_run
#include <stdio.h>
int hf_run(int nx, int ny, int max_steps, const double *tolerance,
void (*report)(int, double, void *), void *context);
typedef struct { int calls; double last; } progress;
static void on_report(int step, double residual, void *context) {
progress *p = context;
p->calls += 1;
p->last = residual;
printf(" C callback: step %d residual %.4f\n", step, residual);
}
int main(void) {
progress p = {0, 0.0};
double tol = 5.0e-2;
printf("with tolerance and callback\n");
int steps = hf_run(21, 21, 1000, &tol, on_report, &p);
printf("steps=%d callbacks=%d last=%.4f\n", steps, p.calls, p.last);
printf("without tolerance (NULL) and without callback\n");
steps = hf_run(21, 21, 1000, NULL, NULL, NULL);
printf("steps=%d\n", steps);
return 0;
}
c_associated(report) is true
c_f_procpointer(report, notify) loop until residual below tolerance R->>K: jacobi_sweep(u, unew, residual) K-->>R: residual opt every 50th step R->>C: notify(step, residual, context) C-->>R: returns (updates progress p) end end R-->>M: steps = 187 M->>R: hf_run(21, 21, 1000, NULL, NULL, NULL) Note over R: present(tolerance) is false, no callback R-->>M: steps = 502
Built with the sources in this order, gfortran for the module and gcc for the link (add -lgfortran -lm), the program
printed the following, and the Flang 23 build gave exactly the same output:
with tolerance and callback
C callback: step 50 residual 0.4422
C callback: step 100 residual 0.1665
C callback: step 150 residual 0.0808
steps=187 callbacks=3 last=0.0808
without tolerance (NULL) and without callback
steps=502
With the tolerance 0.05 supplied the iteration stopped after 187 steps and the callback ran three times (steps 50, 100 and 150);
with NULL for the tolerance the default 0.001 applied and the iteration needed 502 steps, with no callback. A
c_funptr with the value attribute is the declaration used here for the callback because it is the type that
c_associated can test for null; the program passes NULL as the callback in its second call and the Fortran code then
skips it.
Two kinds of program state do not cross the boundary silently. Input/output: Fortran and C each have their own buffers for
standard output, and when both write to the same file the order is processor dependent (18.10.1). The little program
below, fortran_line called from a C main between two printfs with standard output piped to cat, printed these
orders:
subroutine fortran_line(flush_after) bind(c, name='fortran_line')
use, intrinsic :: iso_c_binding, only: c_int
use, intrinsic :: iso_fortran_env, only: output_unit
implicit none
integer(c_int), value, intent(in) :: flush_after
print '(a)', 'Fortran line'
if (flush_after /= 0_c_int) flush (output_unit)
end subroutine fortran_line
#include <stdio.h>
#include <stdlib.h>
void fortran_line(int flush_after);
int main(int argc, char **argv) {
int flush_after = argc > 1 ? atoi(argv[1]) : 0;
printf("C line 1\n");
if (flush_after) fflush(stdout);
fortran_line(flush_after);
printf("C line 2\n");
return 0;
}
== piped, no flush
C line 1
Fortran line
C line 2
== piped, flush
C line 1
Fortran line
C line 2
== flang piped, no flush
Fortran line
C line 1
C line 2
== flang piped, flush
C line 1
Fortran line
C line 2
With nothing flushed, GNU Fortran 16.2 produced the call order, while Flang 23 printed the Fortran line first. After a
flush (and fflush) on each side before control crosses the boundary, both agree. The standard does not promise either
order, so a library that prints from both languages should flush before every call that crosses (or better: write
from one language only). Exceptions and signals: a procedure defined by something other than Fortran must not use
signal to change how a floating-point exception is handled, and must not alter the floating-point status other than by
raising flags (18.10.3), and the flag values on entry to it are processor dependent. stop and error stop end the whole
program, not only the Fortran part; see
Error Handling and Program Termination
and Floating-Point and IEEE Arithmetic.
Linking and building
A mixed program is several object files and one link step, and the question is which driver does it and which runtime libraries it must add:
| Situation | What to do |
|---|---|
C |
link with |
Fortran |
link with |
Flang |
link with |
C++ |
a |
Shared library |
compile the Fortran sources with |
Static library |
|
Mixed compilers |
expose only |
The Fortran runtime of GNU Fortran is a separate library and carries I/O units, formatted output and array runtime
helpers, so a Fortran routine that prints or allocates may fail to link from gcc without -lgfortran. The build system
mechanics (the order of .mod files, add_library, FortranCInterface) are on the pages about
Building with Make, CMake and Meson and
Compilers and Compiler Options.
Where the books are outdated
The book consulted for this page (Metcalf, Reid, Cohen and Bader’s Modern Fortran Explained for Fortran 2023: chapter 20 on interoperability, and the interoperability section of chapter 23 on the Fortran 2023 additions) is a sound introduction to the concepts. Where it, or older Fortran material, differs from current practice:
| Area | Outdated in the books, current approach |
|---|---|
Restrictions of the first design |
the 2003 rules (no |
Strings |
building a C string with |
|
the |
|
the character pointer argument |
Linkage conventions |
trailing underscores, |
Compiler context |
the books say nothing about compilers; this page records what GNU Fortran 16.2 and LLVM Flang 23 accept |
Standard version provenance and compiler support
| Feature | Introduced | Compiler support checked on 2026-10-19 |
|---|---|---|
|
Fortran 2003 (listed under Fortran 2003 in the GNU Fortran implementation status) |
GNU Fortran 16.2 and LLVM Flang 23 build every example here |
|
Fortran 2018 (content of TS 29113, 4.3.3 and the list of new features of J3/23-007r1) |
GNU Fortran documents the support; |
|
Fortran 2023 |
accepted by GNU Fortran 16.2 and LLVM Flang 23 (examples above) |
|
Fortran 2023 |
accepted by both |
|
Fortran 2023 |
accepted by Flang 23, rejected by GNU Fortran 16.2 (not in the module); Flang’s standards-support table lists the string conversion procedures as not implemented, although the build checked here accepts them, and the length it gave differs from 18.2.3.5 |
Diagnosis of a non-interoperable |
text of the standard (C1562-C1567) |
GNU Fortran reports a warning or an error; Flang 23 did not report the cases in this page |
|
extensions |
documented by Flang; GNU Fortran 16.2 accepts |
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 per-compiler support table.
Pitfalls
-
A kind that is not an
iso_c_bindingconstant.integer,realandlogicalof default kind work on one platform and break on another; usec_int,c_double,c_bool. -
valuemissing or extra. The compiler cannot see the C prototype. A missingvaluepasses an address where C wants a number; an extra one passes a number where C wants an address. -
A
character(len=*)dummy argument for achar *. Usecharacter(kind=c_char)withdimension(*); assumed length means a descriptor. -
Forgetting the terminator. A Fortran string passed to a C
const char *withoutc_null_charis read past its end. -
Printing a
c_chararray witha. One element per format item; convert it to a string first. -
Column-major, row-major.
a[i][j]in C isa(j+1, i+1)in Fortran; a transposed loop is correct but slow. -
Dangling
c_loc. The address of a local, automatic or deallocated target; andc_locof an array section that was copied to a temporary. -
Freeing across allocators.
deallocateonmallocmemory, orfreeonallocatememory. -
Linking without the Fortran runtime. an
undefined referenceto a name starting with_gfortran_means-lgfortranis missing. -
Mixing object files of different compilers' modules. Only the
bind(c)names are stable. -
Variable arguments.
printfand other...functions have no interoperable interface (18.3.7). -
Exceptions,
longjmpand signals through Fortran frames. Not permitted (18.10.1). -
I/O ordering. Fortran and C buffers are separate; flush before crossing the boundary when both write to one file.
-
A
c_f_strpointercall with a largerncharsthan the string. Allowed by the standard, but it crashed LLVM Flang 23 in the experiment above; pass the exact length fromstrlen. -
Trusting the compiler to compare interface and prototype. Keep one header, review it with the Fortran interface and test
c_sizeofagainstsizeoffor every shared structure.
See also Best Practices and Style and Common Gotchas and Pitfalls.
References
-
GNU Fortran manual: Naming and argument passing conventions and Further Interoperability of Fortran with C
-
GNU Fortran manual: Options for code generation conventions (
-funderscoring,-fsecond-underscore) and Options to request or suppress errors and warnings (-Wc-binding-type) -
Flang documentation: Extensions and Fortran standards support