Chapter 14 — Key Takeaways (C-Fortran Interoperability)

A one-page reference for iso_c_binding, bind(c), and crossing the language boundary safely.

The two pillars

Tool What it is How you use it
iso_c_binding Intrinsic module of C-interoperable kinds, the c_ptr/c_funptr types, and helper procedures use, intrinsic :: iso_c_binding
bind(c) Attribute giving a procedure/type/variable a clean C linkage name (defeats name mangling) subroutine f(...) bind(c, name="f")

C-interoperable kinds (declare boundary data with these)

Fortran C type Fortran C type
integer(c_int) int real(c_float) float
integer(c_long) long real(c_double) double
integer(c_size_t) size_t complex(c_double_complex) double _Complex
integer(c_int64_t) int64_t logical(c_bool) _Bool
character(kind=c_char) char type(c_ptr) void *

A negative kind constant means "no interoperable kind on this platform." Use c_double/c_int for boundary data even though they equal dp/default integer — say what C means.

The one rule that causes the most bugs: value vs reference

The argument in C is… Fortran dummy Why
a scalar (int n, double x) — by value add value C copies scalars; Fortran must too
a pointer (double *v, struct t *p) — by reference no value (intent(in/inout)) a pointer is Fortran's default passing
  • Missing value on a scalar → an address is read as a value → garbage / crash (compiles fine).
  • Spurious value on an array → C mutates a copy the caller never sees → no effect (and slow).

bind(c) linkage

subroutine step(...) bind(c, name="step_c")   ! symbol: exactly  step_c
subroutine step(...) bind(c)                   ! symbol: lowercase Fortran name

Without bind(c), gfortran mangles: foofoo_, module m's foo__m_MOD_foo (unfindable from C).

c_ptr / c_loc / c_f_pointer (for void * APIs)

Call Direction Note
c_loc(a)c_ptr Fortran object → C address a needs target (or pointer), must be contiguous
c_f_pointer(cp, fp[, shape]) C address → Fortran pointer shape gives an array pointer its extents
c_associated(cp) test .true. if non-null
c_sizeof(x) bytes matches C's sizeof for interoperable objects

Interoperable derived type ↔ C struct

type, bind(c) :: particle_t     ! same field order, types, and (auto) padding as C
  integer(c_int) :: id
  real(c_double) :: x, y, mass
end type

No allocatable/pointer components, no type-bound procedures, no sequence. Keep the C struct and the Fortran type in lockstep, field for field.

The column-major / row-major trap (2-D arrays)

Fortran u(i,j)first index fastest (column-major). C u[i][j]last index fastest (row-major). The same memory is transposed between them. Fix a layout contract, never a per-call transpose: Fortran u(i+1, j+1) = C u[i + j*nx] (fast axis i contiguous on both sides).

Strings

C strings are null-terminated; Fortran strings are not. - Fortran → C: call cfunc(c_char_"text" // c_null_char). - C → Fortran: receive character(kind=c_char) :: s(*), scan do while (s(k) /= c_null_char).

$ gcc -c foo.c
$ gfortran -std=f2018 -Wall bar.f90 foo.o -o prog     # gfortran drives the link

Drive the final link with gfortran so libgfortran is included. Exactly one entry point: a Fortran program or a C main, never both.

Terms first-defined here

iso_c_binding · bind(c) · C-interoperable kind · interoperable derived type (plus the value attribute, and c_ptr/c_loc/c_f_pointer from Ch. 11 put to work).

Project piece added this chapter

An optional bind(c) shim step_c(nx, ny, u, dx, dy, alpha, dt) exposing the solver's step to a C (or Python) driver — the raw array by reference, scalars by value — with the column-major layout contract noted. The pure-Fortran step(field, alpha, dt) is unchanged; field_t can't be bind(c) (its u(:,:) is allocatable), which is why the shim unpacks the field.