本节摘要:Fortran 与 C 互相调用,难点在两边默认的"规矩"不同:传参默认传址对传值、数组列优先对行优先、符号名带下划线对原样导出。ISO C 绑定(iso_c_binding 模块 + bind 子句)把这些差异全部显式化。本节讲清类型映射、bind 名称、双向调用的写法,让你在两门语言之间搭起一条类型安全的桥。
Fortran 调 C 库(比如 BLAS、MPI 的 C 接口、系统 API),或把 Fortran 内核暴露给 C 外壳——历史上这靠编译器特定的"魔法",换个编译器就断。ISO C 绑定(Fortran 2003)把这件事标准化了:类型怎么对应、符号名怎么导出、参数怎么传,全部写清楚,让编译器不再猜。它不消灭两门语言的差异,而是把差异变成显式约定。
核心是 use iso_c_binding 导入类型常量,配合 bind(c) 声明。类型映射表要背熟:c_int 对应整数、c_double 对应双精度、c_float 对应单精度、c_ptr 对应 C 指针、c_char 对应字符。bind(c) 告诉编译器"这个实体按 C 的调用约定导出,符号名就用 bind 里给的名字"。
module interop_mod use iso_c_binding, only: c_int, c_double implicit none ! Fortran 侧声明一个与 C 对齐的接口 interface function c_sqrt(x) bind(c, name="sqrt") result(r) import :: c_double real(c_double), value :: x ! C 按值传参 real(c_double) :: r end function c_sqrt end interface contains ! Fortran 侧导出给 C 调用的子程序 subroutine compute_sum(n, x, s) bind(c, name="compute_sum") integer(c_int), value :: n real(c_double), dimension(n) :: x real(c_double), intent(out) :: s s = sum(x) end subroutine compute_sum end module interop_mod
name="sqrt" 显式指定 C 侧符号名,绕开 Fortran 默认的名称修饰(带下划线等)。value 属性对应 C 的按值传参——C 函数参数默认按值,Fortran 默认传址,在 interface 块里用 value 对齐。dimension(n) 的 x 对应 C 侧一个数组指针,n 是长度,这是 C 里常见的"指针加长度"约定。
上面 interface 块里的 c_sqrt 就是调用 C 标准库 sqrt 的桥。调用的代码和普通函数一样:
program call_c use interop_mod, only: c_sqrt use iso_c_binding, only: c_double implicit none real(c_double) :: v v = c_sqrt(2.0_c_double) write(*,*) 'sqrt(2) =', v end program call_c
要点是 interface 块必须和 C 函数签名严格一致——类型、个数、传值传址都对上。对不上就是运行时内存错乱,Fortran 帮不了你。数组传过去要记住:C 侧看到的是首地址,长度靠 n 参数约定,Fortran 的边界检查救不了跨语言调用。
反向也一样:Fortran 子程序带 bind(c, name) 导出,C 侧按名字声明 extern 再调用。关键是数组布局:Fortran 列优先,C 行优先,二维数组跨语言必须同时转置索引或用一维接口,否则数据错位。
/* C 侧声明并调用 Fortran 导出的 compute_sum */ extern void compute_sum(int n, const double *x, double *s); int main(void) { double x[3] = {1.0, 2.0, 3.0}; double s = 0.0; compute_sum(3, x, &s); printf("sum = %f\n", s); return 0; }

互操作代码要遵循几条纪律。第一,interface 块签名与 C 函数必须逐项对齐,宁可多写 import 也不留默认猜测。第二,二维以上数组跨语言统一用一维接口加显式维度,或在 C 侧处理转置,别指望两边布局自动一致。第三,字符与字符串的跨语言传递最坑,C 以空字符结尾、Fortran 定长,要么用 c_char 数组手管,要么避免在热路径上传字符串。第四,跨语言调用不做边界检查,所有错误都是未定义行为,把检查放在 Fortran 入口内部。这些规矩的共性:把"编译器默认"全部替换成"显式约定"。
阅读完本节,你应当能够:
标量与数组能通了,看更复杂的结构怎么传——7.2 节。