Exercises: C-Fortran Interoperability
These exercises make you write interfaces, predict outputs, and diagnose the silent failures that
dominate mixed-language work — the ones no compiler catches. Several ask for a C and a Fortran side;
sketch both, and where a full program is asked for, compile and run it (the mixed compile pattern is
gcc -c foo.c then gfortran bar.f90 foo.o -o prog).
Difficulty: ⭐ warm-up · ⭐⭐ standard · ⭐⭐⭐ deeper. Solutions: worked solutions to the daggered (†)
and odd-numbered problems are in appendices/answers-to-selected.md; the compilable numeric answers
(14.19, 14.20) are in code/exercise-solutions.f90. Try every problem before you look.
Part A — Warm-ups ⭐
14.1 † Name the intrinsic module and the attribute that are the two pillars of C interoperability, and say in one sentence what each provides.
14.2 Give the iso_c_binding kind you would use to declare a Fortran variable interoperable with each
of these C types: int, double, char, _Bool, long int.
14.3 † You have a real(dp) value that is about to be passed to a C function. Why is it better to
declare the boundary-crossing variable real(c_double) instead, even though dp and c_double are the
same kind on your machine?
14.4 What does the name= clause of bind(c) control? If you write subroutine Flux() bind(c) with
no name=, what is the resulting linker symbol?
Part B — Kinds and Linkage ⭐⭐
14.5 † With gfortran, a module procedure solve inside module linalg is emitted under a decorated
symbol (something like __linalg_MOD_solve). (a) What is this decoration called and why does the compiler
do it? (b) Exactly how does adding bind(c, name="solve") change the symbol, and why does that matter to a
C caller?
14.6 Write the Fortran interface block for this C function so it can be called from Fortran:
float clamp(float lo, float hi, float v);
14.7 † Write the Fortran interface for long factorial(int n);. Which arguments need the value
attribute, and which kinds do you use for the argument and the result?
Part C — Type, Compile, and Run ⭐⭐
Predict the output first, then build and check.
14.8 † The C driver of example-02 is changed to double v[4] = {1.0, 2.0, 3.0, 4.0}; and calls
mean_c(4, v). What does it print? Show your arithmetic.
14.9 A Fortran module exposes function twice(n) bind(c, name="twice") result(m) with
integer(c_int), value :: n and m = 2*n. A C driver calls printf("%d\n", twice(21));. What is printed,
and why does n need value?
14.10 † Port this C routine to a Fortran interface, then write a two-line Fortran program that calls it
on a = [1,2,3], b = [4,5,6] and prints the result. What does it print?
double dot(int n, const double *a, const double *b); /* sum of a[i]*b[i] */
Part D — Port It ⭐⭐
14.11 Here is a Fortran routine. Write the C prototype a driver would use to call it, and one line of C
that calls it on an array w of length 3.
subroutine scale_c(n, w, factor) bind(c, name="scale_c")
use, intrinsic :: iso_c_binding, only: c_int, c_double
integer(c_int), value :: n
real(c_double), intent(inout) :: w(n)
real(c_double), value :: factor
w = factor * w
end subroutine scale_c
14.12 † A Python programmer has a slow pure-Python function sum_squares(v) returning
$\sum_i v_i^2$. Write (a) a Fortran bind(c) routine sum_squares_c(n, v) that computes it and (b) the C
prototype a driver would use. This is the kind of kernel that, wrapped for Python in
Chapter 15, runs far faster than the original.
Part E — Find the Bug ⭐⭐
14.13 † This interface compiles but the call returns nonsense. Diagnose and fix it.
interface
function csquare(x) bind(c, name="csquare") result(r)
use, intrinsic :: iso_c_binding, only: c_double
real(c_double) :: x ! C side is: double csquare(double x);
real(c_double) :: r
end function csquare
end interface
14.14 A C driver fills a $200 \times 100$ grid row-major and passes it to a Fortran routine declared
real(c_double), intent(inout) :: u(200,100). The physics comes out mirrored across the diagonal — a
source placed on the left edge appears on the top. There is no crash. What is wrong, and give two ways to
fix it.
14.15 † A Fortran program calls C's puts through a correct interface, passing
c_char_"progress: 42%", and the terminal shows the text followed by a stream of garbage characters
before it stops. What single thing is missing, and what is the fix?
14.16 Why does the compiler reject this type, and what do you pass to C instead of it?
type, bind(c) :: buffer_t
integer(c_int) :: n
real(c_double), allocatable :: data(:)
end type buffer_t
Part F — Design It ⭐⭐⭐
14.17 † Design the bind(c) interface for a routine that writes the solver's field to a file:
conceptually write_field_c(nx, ny, u, filename). Decide the interoperable type of each argument, and
explain precisely how filename must be handled so the C side and the Fortran side agree on where the
string ends.
14.18 Extend the project's step_c so that, in addition to updating u, it reports the largest
absolute change over the step (useful as a convergence test). Give the new bind(c) interface — what
argument do you add, with which attributes, and why can it not carry the value attribute?
Part G — Back of the Envelope ⭐⭐⭐
14.19 † On an LP64 platform (int = 4 bytes, double = 8 bytes and 8-byte aligned, char = 1 byte),
work out the size in bytes of struct { int a; double b; char c; };, accounting for padding. Confirm your
reasoning against code/exercise-solutions.f90, which reports c_sizeof of the matching interoperable
type.
14.20 You are tempted to transpose a $4000 \times 4000$ array of doubles on every step to bridge a
C/Fortran layout mismatch. Estimate the bytes read and written by one transpose, and the total over a
10,000-step run. Use this to argue for agreeing a layout contract (§14.5) instead. (Numeric answer in
code/exercise-solutions.f90.)
Part H — Interleaved ⭐⭐
Mixing this chapter with earlier ones.
14.21 † (Chapter 3) State the relationship between selected_real_kind(15, 307) and c_double: are
they the same kind, and if so, why keep two names for it?
14.22 (Chapter 6) In a bind(c) procedure, why must an array dummy be explicit-shape (v(n)) rather
than the assumed-shape (v(:)) that Chapter 6 recommended for ordinary modern Fortran?
14.23 † (Chapter 11) c_loc(a) requires a to have the target attribute. Explain why, connecting it
to what target means for an ordinary Fortran pointer.
14.24 (Chapter 9) Which single feature of the field_t derived type prevents it from being declared
type, bind(c)? Given that, what do you pass across the boundary instead when a C driver needs to step the
field?
14.25 † (Chapters 11 & 6) Advanced. C's qsort takes a comparison callback:
void qsort(void *base, size_t n, size_t size, int (*cmp)(const void *, const void *));. Sketch, in
words and skeleton code, how you would sort a Fortran integer array with it: which iso_c_binding
procedures produce the two pointers you must pass (the data pointer and the function pointer), and what the
Fortran comparator's interface looks like (its two arguments' types, and how it recovers the integers to
compare). You need not produce a fully working program.
Solutions to the daggered and odd-numbered problems are in appendices/answers-to-selected.md; the
compilable numeric answers (14.19, 14.20) are in code/exercise-solutions.f90. The interface-design
problems have more than one acceptable answer — the appendix gives a model.