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
valueon a scalar → an address is read as a value → garbage / crash (compiles fine). - Spurious
valueon 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: foo → foo_, 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).
Compile & link (memorize this pattern)
$ 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.