Fortran 与 C 数组传递的三种方式

2023-01-11 18:50:02 浏览数 (1)

本文由知乎答主清风徐来提供,点击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)valuetargetfunction语句写起来都很长,效率挺低的,字符串能力弱是刻在基因里的。

当然了,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)

0 人点赞