ZetCode

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.

c1.f90
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.

add.c
#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.

c2.f90
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.

greet.c
#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.

c3.f90
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.

arrays.c
#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.

c4.f90
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.

apply.c
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.

c5.f90
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.

point.c
#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.

c6.f90
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.

forlib.f90
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.

main.c
#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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Fortran tutorials.