本文由知乎答主清风徐来提供,点击https://zhuanlan.zhihu.com/p/519709168即可跳转阅读。
01 背景
在群里闲聊的时候,有群友提出(:)
不能作为 Fortran 接口传递数组给 C,于是基于经验进行了以下的尝试和解析(可能不对,欢迎指正)。
02 Fortran 数组
在高级编程语言初期,Fortran 数组设计与 C 是一致的,只要拿到数组第一个元素的地址即可,相匹配上;但随着 Fortran 在科学计算领域的发展,其没有实现链表、哈希等内置数据结构,却在数组这种适用于科学计算(矩阵线性代数)上花了不少设计,导致 F77 array(*) 与 F90 array(:) 这两种风格不同,前者与 C 兼容,实际上是地址引用(指针),后者则是 Fortran 语言的特有内置数据结构!
03 Fortran 传递数组给 C
从 02 可以推断,如果需要将 Fortran 数组传递给 C,还得是指针(地址),直接传内置数据结构(结构体)是不行的。以下给出三种传递方式,并开放在 Gitee 上:
- Fortran 与 C 数组传递的三种方式 (gitee.com)
- (https://gitee.com/zoziha/fortran-array-to-c)
C语言代码:
代码语言:javascript复制// 获取两者最大值
int max(int *two_int)
{
int result;
if (two_int[0] > two_int[1])
result = two_int[0];
else
result = two_int[1];
// 检查是否传递到 C 的数组值是对的
printf("%d,%d", two_int[0], two_int[1]);
return result;
}
Fortran 语言代码:
代码语言:javascript复制!> author: 左志华
!> date: 2022-05-25
program main
use, intrinsic :: iso_c_binding
!> 三种接口
interface
!> 接口 1:(*)
integer function max_1(two_int) bind(c, name="max")
integer, intent(in) :: two_int(*)
end function max_1
!> 接口 2(不推荐):(1)
integer function max_2(two_int) bind(c, name="max")
integer, intent(in) :: two_int(1)
end function max_2
!> 接口 3:c_ptr
integer function max_3(two_int) bind(c, name="max")
import
type(c_ptr), intent(in), value :: two_int
end function max_3
end interface
print *, max_1([1, 2])
print *, max_2([1, 2])
block
integer, target :: i(2) = [1, 2]
print *, max_3(c_loc(i))
end block
end program main
!>> fpm run
! 1,2 2
! 1,2 2
! 1,2 2
一个可行的简单示例胜过千言万语,到此就结束了!
04 评论
Fortran 内置数组数据结构,优劣并存,只是希望 Fortran 与其他语言相融相通,越来越好;这一小段代码,相比 C,发现Fortran 写代码还是有点繁琐的,intent(in)
、value
和target
,function
语句写起来都很长,效率挺低的,字符串能力弱是刻在基因里的。
当然了,Fortran 与 C 函数可以通过指针(地址)传递数组,Fortran 与 Fortran 函数传递的方式,肯定也包括以上三种,以及新范式(:)
的传递方式。
05 番外:在 Fortran 中访问 C 的本地数组变量
本贴原来主要关注在函数接口中传递数组(即访问函数堆栈中的数组变量),但有些人对在 Fortran 中访问 C 的本地数组变量感兴趣。
- 从 Fortran 中访问 C 的本地数组变量 (gitee.com)
- (https://gitee.com/zoziha/c2f-demo)
这时候一般分为两种情况,数组和数组指针。先讨论数组:
C 语言代码:
代码语言:javascript复制double x[3]; // 数组
void init()
{
x[0] = 1;
x[1] = 2;
x[2] = 3;
}
void prt()
{
printf(" 在 C 中打印:%f ", x[0]);
printf("%f ", x[1]);
printf("%fn", x[2]);
}
Fortran 语言代码:
代码语言:javascript复制module demo
implicit none
real(8), bind(c) :: x(3)
interface
subroutine init() bind(c)
end subroutine init
end interface
interface
subroutine prt() bind(c)
end subroutine prt
end interface
end module demo
!> author: 左志华
!> date: 2022-10-06
program main
use demo
print *, "从 Fortran 读取 C 本地数组的方式 1:"
call init() ! 赋初值 1,2,3
print *, '值:', x ! 从 Fortran 中访问 bind(c) 数组
call prt() ! 从 C 例程中访问数组
end program main
! 从 Fortran 读取 C 本地数组的方式 1:
! 值:1.0000000000000000 2.0000000000000000 3.0000000000000000
! 在 C 中打印:1.000000 2.000000 3.000000
这里在 Fortran 中绑定 C 中的同名数组,从而直接访问 C 数组。
接下来,讨论数组指针,这略微有点不同:
C 语言代码:
代码语言:javascript复制double *y; // 数组指针
void init2()
{
y = (double *)malloc(3 * sizeof(double));
y[0] = 4;
y[1] = 5;
y[2] = 6;
}
void prt2()
{
printf(" 在 C 中打印:%f ", y[0]);
printf("%f ", y[1]);
printf("%fn", y[2]);
}
Fortran 语言代码:
代码语言:javascript复制module demo2
use iso_c_binding
implicit none
real(8), pointer :: x2(:)
type(c_ptr), bind(c, name='y') :: x_in_demo2
interface
subroutine init2() bind(c)
end subroutine init2
end interface
interface
subroutine prt2() bind(c)
end subroutine prt2
end interface
end module demo2
!> author: 左志华
!> date: 2022-10-06
program main
use iso_c_binding
use demo2
print *, "从 Fortran 读取 C 本地数组的方式 2:"
call init2() ! 赋初值 4,5,6
call c_f_pointer(x_in_demo2, x2, shape=[3]) ! 从 C 中访问 bind(c) 数组
print *, '地址:', x_in_demo2 ! 数组地址,即指针
print *, '值:', x2 ! 将 Fortran 数组指针绑定到 C 数组地址
call prt2() ! 从 C 例程中访问数组
end program main
! 从 Fortran 读取 C 本地数组的方式 2:
! 地址:2205703485936
! 值:4.0000000000000000 5.0000000000000000 6.0000000000000000
! 在 C 中打印:4.000000 5.000000 6.000000
因为 C 中是数组指针,所以 Fortran 也是数组指针,多一个c_f_pointer
绑定指针的操作。
PS. 还是回到我的编程哲学,编程只是一种语言表达性的体现,不断地描述问题,所以代码冗长,但代码背后逻辑却十分简单。
其他链接
- FAQ之 三种数组传递方式 - Fortran教程 - Fortran Coder 程序员聚集地 (fcode.cn)