Calling C Functions from Fortran with ISO_C_BINDING: A Practical Guide
Step-by-step guide to calling C functions from Fortran using ISO_C_BINDING: interface declaration, type mapping, compilation, linking, and validation with concrete examples and troubleshooting.
20 Sept 2025, 16:08 UTC

Desired Outcome
A Fortran program successfully calls a C function—such as a math routine from a C library—with correct argument passing, return value handling, and no segmentation faults or data corruption.
Prerequisites
- A Fortran compiler supporting Fortran 2003 or later:
gfortran≥ 4.3,ifort≥ 11,nvfortran, orflang. - A C compiler compatible with the same ABI (typically
gcc,clang, oriccon the same platform). - The target C library headers and binaries installed (or a simple C source file you control).
- Basic familiarity with Fortran modules and C function prototypes.
Procedure
1. Write the C Function and Header
Create a minimal C library. For this guide we use a simple square-root wrapper:
/* clib.h */
#ifndef CLIB_H
#define CLIB_H
double c_sqrt(double x);
#endif
/* clib.c */
#include
#include "clib.h"
double c_sqrt(double x) {
return sqrt(x);
}
Compile the C code to an object file:
gcc -c -O2 -std=c11 clib.c -o clib.o
Where to run: Any POSIX shell (Linux, macOS, WSL). Permissions: Write access to the working directory. Risk: Low—compilation only.
2. Declare the Fortran Interface Using ISO_C_BINDING
In your Fortran source, use the intrinsic iso_c_binding module and an interface block with bind(C):
! main.f90
program call_c_sqrt
use, intrinsic :: iso_c_binding
implicit none
interface
function c_sqrt(x) bind(C, name="c_sqrt")
import :: c_double
real(c_double), value :: x
real(c_double) :: c_sqrt
end function c_sqrt
end interface
real(c_double) :: x, y
x = 2.0_c_double
y = c_sqrt(x)
print *, "c_sqrt(", x, ") = ", y
end program call_c_sqrt
Key points:
import :: c_doublebrings the C-compatible kind parameter into the interface scope.valueattribute on the dummy argument passes the scalar by value, matching C'sdouble xparameter.bind(C, name="c_sqrt")tells the Fortran compiler to look for the exact symbolc_sqrt(no underscore mangling).
3. Compile the Fortran Source
gfortran -c -O2 main.f90 -o main.o
Check: Verify main.o exists and contains the expected symbol reference:
nm main.o | grep c_sqrt
# Expected: U c_sqrt (undefined, to be resolved at link time)
4. Link Fortran and C Objects Together
Use the Fortran compiler as the linker driver so it pulls in the Fortran runtime (libgfortran, etc.):
gfortran main.o clib.o -o app
Where to run: Same shell. Risk: Linker errors if ABIs mismatch (32 vs 64 bit, different calling conventions).
Expected Checks
- Run the executable:
./appshould output something likec_sqrt( 2.0000000000000000 ) = 1.4142135623730951. - Compare against a pure-Fortran reference: Add
print *, "sqrt(", x, ") = ", sqrt(x)and verify the two results match to machine precision. - Inspect argument values: Add print statements before and after the call, or run under
gdb: gdb ./app thenbreak main,run,print x,step,print y. - Confirm no abnormal termination: Exit code 0, no
Segmentation faultorBus error.
Common Pitfalls and Recovery Options
Linker Fails with "Undefined Reference"
- Cause: Symbol name mismatch (e.g., C compiler emits
c_sqrtbut Fortran expectsc_sqrt_or vice versa). - Fix: Use
bind(C, name="exact_symbol")to override. Inspect actual symbols withnm clib.o | grep c_sqrt. - Cause: Architecture mismatch (32-bit object linked into 64-bit executable).
- Fix: Ensure both compilers target the same architecture:
gcc -m64andgfortran -m64(or-m32for both).
Runtime Crash or Wrong Results
- Type/kind mismatch: Fortran
real(c_double)vs Cdoubleis correct;real(default kind) ordouble precisionmay differ on some platforms. Always usec_double,c_int,c_floatfromiso_c_binding. - Array layout: Fortran is column-major; C is row-major. For 1-D arrays this doesn't matter. For multi-dimensional arrays, either transpose in Fortran before passing, or write the C function to expect column-major order.
- Missing
valueattribute: Withoutvalue, Fortran passes scalars by reference (pointer), causing the C function to read an address as adouble. - Intent attributes: Use
intent(in)for read-only arrays,intent(out)orintent(inout)for outputs. Thevalueattribute impliesintent(in)for scalars.
Character String Handling
C expects null-terminated char*; Fortran strings are fixed-length without a terminator. For Fortran 2018+:
use, intrinsic :: iso_c_binding
character(kind=c_char), allocatable :: c_str
c_str = "hello" // c_null_char
call c_func(c_str)
For older standards, manually append c_null_char to a character(kind=c_char), dimension(:) array and pass the address of the first element.
Variadic Functions, Function Pointers, C++ Mangling
ISO_C_BINDING (Fortran 2003) does not support these directly. Write a C shim function with a fixed prototype that calls the variadic function or C++ method, then bind to the shim.
Verification Checklist
| Check | Command / Method | Pass Criteria |
|---|---|---|
| Symbol names match | nm clib.o and nm main.o | grep U | Undefined in Fortran object matches defined in C object exactly |
| ABI compatibility | objdump -d main.o | head -30 or compile with -S to inspect assembly | Calling convention matches (System V AMD64 on Linux x86_64, Microsoft x64 on Windows) |
| Numerical correctness | Run program, compare c_sqrt(x) vs sqrt(x) | Relative difference < 1e-15 for double precision |
| No memory errors | valgrind ./app (Linux) | No invalid reads/writes, no leaks from the call |
Limitations
- This guide covers Fortran 2003 ISO_C_BINDING. Fortran 2018 adds
CFI_cdesc_tdescriptors for assumed-shape/allocatable arrays andc_f_strptr/c_f_pointerfor strings—those are not covered here. - Compiler-specific extensions (Intel's
!DEC$ ATTRIBUTES, GNU'sbind(C)extensions) are avoided; the examples use only standard syntax. - Linking with C++ libraries requires
extern "C"wrappers and using the C++ compiler as the linker driver (or explicitly adding-lstdc++).
Minimal Complete Example for Testing
Save these three files in one directory, then run the commands below:
# clib.h
#ifndef CLIB_H
#define CLIB_H
double c_sqrt(double x);
#endif
# clib.c
#include
#include "clib.h"
double c_sqrt(double x) { return sqrt(x); }
! main.f90
program call_c_sqrt
use, intrinsic :: iso_c_binding
implicit none
interface
function c_sqrt(x) bind(C, name="c_sqrt")
import :: c_double
real(c_double), value :: x
real(c_double) :: c_sqrt
end function
end interface
real(c_double) :: x, y
x = 2.0_c_double
y = c_sqrt(x)
print *, "C result: ", y
print *, "Fortran reference: ", sqrt(x)
end program
gcc -c -O2 -std=c11 clib.c -o clib.o
gfortran -c -O2 main.f90 -o main.o
gfortran main.o clib.o -o app
./app
Expected output (values may vary in last digit):
C result: 1.4142135623730951
Fortran reference: 1.4142135623730951
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.