Fortran C interoperability
last modified November 14, 2026
C and Fortran describe numbers differently. A C int is whatever
the compiler chooses, a Fortran integer has a kind, and a string is
a pointer to bytes in one language and a fixed-length array of characters in the
other. The module iso_c_binding, part of Fortran 2003, supplies
named kinds whose sizes match the C types and a set of procedures for passing
addresses and procedure pointers between the two languages. With it, Fortran
can call a C library and C can call a Fortran routine, and the compiler checks
the declaration on the Fortran side.
This article matches the kinds, calls C functions that take and return numbers,
passes strings with their terminator, sends an array to C and takes a pointer
back, hands a Fortran procedure to C as a callback, shares a structure declared
with bind(c), and finally writes a Fortran library that a C
main calls. Every program is compiled with gfortran, the C files
with gcc, and the two are linked into one executable.
Kinds that match C
iso_c_binding defines c_int, c_long,
c_float, c_double, c_char,
c_bool and the rest with the size and representation that the C
compiler on the same machine uses. A variable declared with one of them can be
passed to C without conversion, and c_sizeof reports its size in
bytes, which is a quick check that the two sides agree. The constant
c_null_char is the zero byte that ends a C string.
program c1
use iso_c_binding, only: c_int, c_double, c_char, c_null_char, c_sizeof
implicit none
integer(c_int) :: n
real(c_double) :: x
character(kind=c_char, len=16) :: text
n = 42_c_int
x = 2.5_c_double
text = 'fortran'//c_null_char
print '(a, i0)', 'c_int bytes: ', c_sizeof(n)
print '(a, i0)', 'c_double bytes: ', c_sizeof(x)
print '(a, a)', 'the string holds: ', text(1:7)
print '(a, l1)', 'the terminator is zero: ', iachar(text(8:8)) == 0
print '(a, f0.3)', 'a c_double holds: ', x
end program c1
The two sizes confirm the usual conventions of this platform: four bytes for
c_int and eight for c_double. The string is assembled
from the Fortran literal and the null character, and the test of the eighth
character proves that the terminator is a byte with the value zero, not a space.
Printing only the first seven characters keeps the terminal clean; a
write of the whole variable would send the zero byte and the
padding after it.
$ gfortran -std=f2018 -Wall -Wextra -fcheck=all c1.f90 -o c1 $ ./c1 c_int bytes: 4 c_double bytes: 8 the string holds: fortran the terminator is zero: T a c_double holds: 2.500
Calling C functions
A C function is declared inside an interface block with
bind(c, name='...'), which tells the compiler the external name and
the C calling convention. Scalar arguments that C takes by value are declared
with the value attribute, and an array argument is declared with an
assumed size, values(*), because C passes a pointer to the first
element. The C source below holds the three functions used by the program.
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
double scale(double x, double factor)
{
return x * factor;
}
void fill(double *values, int n, double start)
{
for (int i = 0; i < n; i++)
values[i] = start + i;
}
The three functions cover the three shapes that matter: two scalars in and one
out, two doubles in and one out, and an array that the callee fills. The names
in the Fortran interface are the C names, and nothing connects them except the
name string, so a typo is a link error rather than a runtime
surprise.
program c2
use iso_c_binding, only: c_int, c_double
implicit none
integer(c_int) :: a, b, total
real(c_double) :: x, y
real(c_double) :: v(5)
interface
function add(a, b) bind(c, name='add') result(total)
import :: c_int
integer(c_int), value :: a, b
integer(c_int) :: total
end function add
function scaled(x, factor) bind(c, name='scale') result(y)
import :: c_double
real(c_double), value :: x, factor
real(c_double) :: y
end function scaled
subroutine fill(values, n, start) bind(c, name='fill')
import :: c_double, c_int
integer(c_int), value :: n
real(c_double), value :: start
real(c_double) :: values(*)
end subroutine fill
end interface
a = 20_c_int
b = 22_c_int
total = add(a, b)
print '(a, i0)', 'add(20, 22) = ', total
x = 3.0_c_double
y = scaled(x, 2.5_c_double)
print '(a, f0.2)', 'scaled(3.0, 2.5) = ', y
call fill(v, 5_c_int, 10.0_c_double)
print '(a, 5f6.1)', 'fill produced: ', v
end program c2
The interface of add takes two value integers, which
matches C's int add(int a, int b); without value the
Fortran side would pass addresses and C would add two pointers. The subroutine
fill receives the address of the first element and a count, and
writes the whole array, which Fortran then prints. The calls behave like
ordinary Fortran calls, because that is what the interface makes them.
$ gcc -std=c11 -Wall -Wextra -c add.c -o add.o $ gfortran -std=f2018 -Wall -Wextra -fcheck=all c2.f90 add.o -o c2 $ ./c2 add(20, 22) = 42 scaled(3.0, 2.5) = 7.50 fill produced: 10.0 11.0 12.0 13.0 14.0
Strings across the boundary
A Fortran character variable knows its length; a C string ends at the first zero
byte and knows nothing else. The bridge is an array of
character(kind=c_char) that ends with c_null_char,
passed as name(*). On the way back, a C function that returns a
length counts the bytes before the zero, while len_trim counts
everything that is not a blank, including that zero.
#include <stdio.h>
#include <string.h>
void greet(const char *name)
{
printf("hello, %s!\n", name);
}
int name_length(const char *name)
{
return (int) strlen(name);
}
The C functions read the buffer until the terminator, which is why the Fortran
side must supply one. The strlen call returns seven for
Fortran, the number of visible characters.
program c3
use iso_c_binding, only: c_char, c_null_char, c_int
implicit none
character(kind=c_char, len=32) :: name
interface
subroutine greet(name) bind(c, name='greet')
import :: c_char
character(kind=c_char), intent(in) :: name(*)
end subroutine greet
function name_length(name) bind(c, name='name_length') result(n)
import :: c_char, c_int
character(kind=c_char), intent(in) :: name(*)
integer(c_int) :: n
end function name_length
end interface
name = 'Fortran'//c_null_char
call greet(name)
print '(a, i0)', 'the C length function says ', name_length(name)
print '(a, i0)', 'len_trim without the terminator: ', len_trim(name)
end program c3
The two lengths differ by exactly one, and the difference is the terminator:
len_trim stops at the last non-blank character, and the null byte is
not a blank, so it is included. A program that prints the character variable
directly would emit that byte and the padding behind it; printing
name(1:len_trim(name) - 1), or keeping the length returned by C,
avoids it. The same rule applies in the other direction, where a Fortran routine
that fills a C buffer must append the zero itself.
$ gcc -std=c11 -Wall -Wextra -c greet.c -o greet.o $ gfortran -std=f2018 -Wall -Wextra -fcheck=all c3.f90 greet.o -o c3 $ ./c3 hello, Fortran! the C length function says 7 len_trim without the terminator: 8
Arrays and pointers
An array of interoperable type can be passed to C by its first element, and
c_loc returns its address as a c_ptr. That pointer can
be turned back into a Fortran pointer with c_f_pointer, which is
how a program receives memory that C allocated or holds a view of a buffer with
a different shape. The array must be contiguous and must have the
target attribute for c_loc to accept it.
#include <stddef.h>
void ramp(double *values, int n)
{
for (int i = 0; i < n; i++)
values[i] = 3.0 * i + 1.0;
}
double dot(const double *a, const double *b, int n)
{
double total = 0.0;
for (int i = 0; i < n; i++)
total += a[i] * b[i];
return total;
}
ramp writes a linear sequence into the buffer and dot
multiplies two arrays element by element, both of them ordinary C loops over a
pointer. Two-dimensional arrays are the one place where the layouts disagree:
Fortran stores a matrix column by column and C row by row, so a matrix passed
without thought arrives transposed.
module cbridge
use iso_c_binding, only: c_double, c_int
implicit none
interface
subroutine ramp(values, n) bind(c, name='ramp')
import :: c_double, c_int
integer(c_int), value :: n
real(c_double), intent(out) :: values(*)
end subroutine ramp
function dot(x, y, n) result(total) bind(c, name='dot')
import :: c_double, c_int
integer(c_int), value :: n
real(c_double), intent(in) :: x(*), y(*)
real(c_double) :: total
end function dot
end interface
end module cbridge
program c4
use iso_c_binding, only: c_double, c_int, c_loc, c_f_pointer
use cbridge
implicit none
real(c_double), allocatable, target :: a(:)
real(c_double), pointer :: p(:)
allocate (a(6))
a = 0.0_c_double
call ramp(a, 6_c_int)
print '(a, 6f6.1)', 'the array after the C call: ', a
print '(a, f0.1)', 'the dot product by the C function: ', &
dot(a, a, 6_c_int)
call c_f_pointer(c_loc(a), p, [6])
print '(a, f0.1)', 'the first element through the pointer: ', p(1)
print '(a, f0.1)', 'the last element through the pointer: ', p(6)
end program c4
The interface lives in a module so that its shape is checked at every call, and
the C functions fill and read the array that Fortran allocated. The dot product
of the ramp with itself is 591, which is the sum of the squares of the six
values. The pointer created by c_f_pointer aliases the same memory,
so p(1) is the first element and p(6) the last, and
the array can be passed to any routine that expects a pointer of that shape.
$ gcc -std=c11 -Wall -Wextra -c arrays.c -o arrays.o $ gfortran -std=f2018 -Wall -Wextra -fcheck=all c4.f90 arrays.o -o c4 $ ./c4 the array after the C call: 1.0 4.0 7.0 10.0 13.0 16.0 the dot product by the C function: 591.0 the first element through the pointer: 1.0 the last element through the pointer: 16.0
Passing a procedure to C
C takes a function pointer as an argument, and Fortran can supply one.
c_funloc(procedure) returns a c_funptr for a procedure
declared with bind(c), and the C side calls it through the pointer.
The Fortran procedure must use value arguments for the scalars,
because that is how C calls a function, and its result must be an interoperable
type.
double apply(double (*f)(double), double x)
{
return f(x);
}
double twice(double (*f)(double), double x)
{
return f(f(x));
}
apply calls the function once and twice calls it twice
in a row, which is enough to show that the pointer is a real callable address
and not a copy of anything: the second function composes the first with itself.
module callbacks
use iso_c_binding, only: c_double, c_funptr
implicit none
interface
function apply(f, x) bind(c, name='apply') result(y)
import :: c_funptr, c_double
type(c_funptr), value :: f
real(c_double), value :: x
real(c_double) :: y
end function apply
function twice(f, x) bind(c, name='twice') result(y)
import :: c_funptr, c_double
type(c_funptr), value :: f
real(c_double), value :: x
real(c_double) :: y
end function twice
end interface
contains
function square(x) bind(c) result(y)
real(c_double), value :: x
real(c_double) :: y
y = x * x
end function square
function increment(x) bind(c) result(y)
real(c_double), value :: x
real(c_double) :: y
y = x + 1.0_c_double
end function increment
end module callbacks
program c5
use iso_c_binding, only: c_double, c_funloc
use callbacks
implicit none
print '(a, f0.1)', 'C calls square(3): ', &
apply(c_funloc(square), 3.0_c_double)
print '(a, f0.1)', 'C calls increment(41): ', &
apply(c_funloc(increment), 41.0_c_double)
print '(a, f0.1)', 'square applied twice: ', &
twice(c_funloc(square), 3.0_c_double)
end program c5
Two Fortran functions become two C callbacks, and C applies them to different
arguments without knowing what they do. The last line squares three and then
squares the result, giving 81. The reverse direction, a C function passed to a
Fortran routine that expects a procedure argument, uses
c_f_procpointer and is the mirror image of this.
$ gcc -std=c11 -Wall -Wextra -c apply.c -o apply.o $ gfortran -std=f2018 -Wall -Wextra -fcheck=all c5.f90 apply.o -o c5 $ ./c5 C calls square(3): 9.0 C calls increment(41): 42.0 square applied twice: 81.0
Structures
A derived type declared with bind(c) has the memory layout of a C
structure with the same members in the same order, and its components must be
interoperable types. Passing such a type to a C function without
value passes its address, so the callee can change the members, and
the change is visible in Fortran after the call. C's own structure is declared
in the source below.
#include <math.h>
struct record {
int id;
double value;
};
double adjust(struct record *r)
{
r->value = r->value * 2.0 + r->id;
return r->value;
}
double distance(double x1, double y1, double x2, double y2)
{
double dx = x2 - x1;
double dy = y2 - y1;
return sqrt(dx * dx + dy * dy);
}
adjust receives a pointer to the structure, changes the value and
returns the new one, and distance is a plain function of four
doubles that needs no structure at all.
module types
use iso_c_binding, only: c_int, c_double
implicit none
type, bind(c) :: record
integer(c_int) :: id
real(c_double) :: value
end type record
interface
function adjust(r) bind(c, name='adjust') result(v)
import :: record, c_double
type(record) :: r
real(c_double) :: v
end function adjust
function distance(x1, y1, x2, y2) bind(c, name='distance') result(d)
import :: c_double
real(c_double), value :: x1, y1, x2, y2
real(c_double) :: d
end function distance
end interface
end module types
program c6
use types
implicit none
type(record) :: r
r%id = 7_c_int
r%value = 1.5_c_double
print '(a, f0.2)', 'the value before the call: ', r%value
print '(a, f0.2)', 'the C function returned: ', adjust(r)
print '(a, i0)', 'the id is unchanged: ', r%id
print '(a, f0.2)', 'the value after the call: ', r%value
print '(a, f0.4)', 'the distance from (0,0): ', &
distance(0.0_c_double, 0.0_c_double, 3.0_c_double, 4.0_c_double)
end program c6
The type has exactly two members, an integer and a double, in the order of the C
structure, and the interface names the same C functions as before. The value
doubles and gains the id, and the new value of 10.0 is visible on the Fortran
side because the structure was passed by reference. The distance of the point
(3, 4) from the origin is 5.0, a check that the value arguments
were passed as C expects them.
$ gcc -std=c11 -Wall -Wextra -c point.c -o point.o $ gfortran -std=f2018 -Wall -Wextra -fcheck=all c6.f90 point.o -o c6 $ ./c6 the value before the call: 1.50 the C function returned: 10.00 the id is unchanged: 7 the value after the call: 10.00 the distance from (0,0): 5.0000
Calling Fortran from C
The other direction needs a Fortran procedure with bind(c), which
gives it a C name and a C calling convention. Only procedures in a module or
external procedures can carry the attribute; a module procedure is reached from C
by the name in its bind attribute, and its module has to be
compiled first so that the object file contains it. The C program declares the
function with a prototype and links the object.
module forlib
use iso_c_binding, only: c_int, c_double
implicit none
contains
subroutine mean(values, n, result) bind(c, name='mean_value')
integer(c_int), value :: n
real(c_double), intent(in) :: values(*)
real(c_double), intent(out) :: result
integer :: i
real(c_double) :: total
total = 0.0_c_double
do i = 1, int(n)
total = total + values(i)
end do
result = total / real(n, c_double)
end subroutine mean
end module forlib
The subroutine mean_value takes an array, its length and an output
scalar, all declared with C kinds. The length is value because C
passes an int, the array is assumed size, and the result is written
through a pointer. The module wrapper is not visible to C: only the
bind name is.
#include <stdio.h>
void mean_value(const double *values, int n, double *result);
int main(void)
{
double values[5] = {2.0, 4.0, 6.0, 8.0, 10.0};
double result;
mean_value(values, 5, &result);
printf("the mean is %.2f\n", result);
return 0;
}
The C program declares the function, fills an array and prints the result. The
build compiles each language with its own compiler and links with gcc, adding
-lgfortran for the Fortran runtime that the object file needs. The
mean of 2, 4, 6, 8 and 10 is 6, which is what the program prints.
$ gcc -std=c11 -Wall -Wextra -c main.c -o main.o $ gfortran -std=f2018 -Wall -Wextra -fcheck=all -c forlib.f90 -o forlib.o $ gcc main.o forlib.o -o cmain -lgfortran $ ./cmain the mean is 6.00
Next steps
The derived types article explains the
types that bind(c) makes interoperable, the
arrays article covers the column-major order that
differs from C, and the
procedure arguments article explains
the procedure pointers that c_funloc turns into C function
pointers. The binary files article shows
the same bytes in a file, and the
compiling and debugging article
covers the separate compilation and linking used in every transcript here.
Source
Fortran 2018 standard (J3/18-007r1)
GNU Fortran manual: Interoperability with C
GNU Fortran manual: ISO_C_BINDING
In this article we have matched the C kinds with iso_c_binding and
checked their sizes, called C functions with scalar and array arguments,
passed a terminated string and seen the terminator counted by
len_trim, moved an array to C and viewed it again through
c_f_pointer, handed two Fortran functions to C as callbacks,
shared a bind(c) structure by reference, and built a Fortran
subroutine that a C main calls. The previous article,
Fortran coarrays, moves data between images of
one program; this one moves it between two languages.
Author
List all Fortran tutorials.