我目前正在对某些Fortran程序进行现代化改造,我想用签名
围绕旧样式的Fortran 77例程编写包装器SUBROUTINE INITMATRIX( M , N , A , LDA)
INTEGER M, N, LDA
DOUBLE PRECISION A(LDA,*)
对于该例程,我编写了一个包装,将Fortran 90样式矩阵作为输入
SUBROUTINE INITMATRIX_F90( A )
DOUBLE PRECISION, INTENT(INOUT) :: A(:,:)
INTEGER :: M, N, LDA
M = SIZE(A,1)
N = SIZE(A,2)
LDA = SIZE(A,1)
CALL INITMATRIX(M, N, A, LDA)
END SUBROUTINE
这很好用,除非我将一个片段传递给例程。例如,我有一个20 x 20的矩阵,我只想初始化前10行。然后我会打电话给
DOUBLE PRECISION A(20,20)
CALL INITMATRIX_F90(A(1:10,1:20))
这会导致错误,因为我的包装器获取了错误的数组前导尺寸。在示例中,我使用LDA=10
而不是LDA = 20
。有没有办法访问数组的跨步/扩展来恢复前导维?关于ISO_Fortran_binding.h头文件与C的互操作性,该信息存储在数组描述符中。
为了可视化问题,下面是MWE对问题进行降级处理。
PROGRAM MAIN
INTERFACE
SUBROUTINE INITMATRIX_F90(A)
DOUBLE PRECISION, INTENT(INOUT) :: A(:,:)
END SUBROUTINE
END INTERFACE
DOUBLE PRECISION :: A(20,20)
A = 1.0D0
CALL INITMATRIX_F90(A(1:10,1:20))
END PROGRAM MAIN
SUBROUTINE INITMATRIX_F90( A )
USE ISO_C_BINDING
IMPLICIT NONE
DOUBLE PRECISION, INTENT(INOUT), POINTER :: A(:,:)
INTEGER :: M, N, LDA
TYPE(C_PTR) :: LOC1, LOC2
INTEGER*16 :: LOCX1, LOCX2
CHARACTER*32 :: TMP
M = SIZE(A,1)
N = SIZE(A,2)
WRITE(TMP, *) C_LOC(A(1,1))
READ(TMP, *) LOCX1
WRITE(TMP, *) C_LOC(A(1,2))
READ(TMP, *) LOCX2
LDA = (LOCX2-LOCX1) / C_SIZEOF(A(1,1))
WRITE(*,*) "M = ", M
WRITE(*,*) "N = ", N
WRITE(*,*) "LOC = ", LOCX1, LOCX2
WRITE(*,*) "LDA(COMPUTED) = ", LDA
END SUBROUTINE
(我知道接口中缺少该指针,只有在该指针中才能使C_LOC工作。)
输出为
M = 10
N = 20
LOC = 140721770410864 140721770411024
LDA(COMPUTED) = 20
显然,领先维度是通过肮脏的黑客程序正确计算的。 GNU Fortran编译器使用的内部结构,或ISO C <-> Fortran绑定(与GNU使用的结构不同)都包含信息,因此如何从Fortran中访问它们就不会有肮脏的技巧。
另一个MWE是围绕LAPACK的DLASET的以下包装:
PROGRAM MAIN
INTERFACE
SUBROUTINE INITMATRIX2_F90(A)
DOUBLE PRECISION, INTENT(INOUT) :: A(:,:)
END SUBROUTINE
END INTERFACE
DOUBLE PRECISION :: A(20,20)
A = 0.0D0
CALL INITMATRIX2_F90(A(1:10,1:20))
WRITE(*,*) A(1:20,1)
END PROGRAM MAIN
SUBROUTINE INITMATRIX2_F90( A )
USE ISO_C_BINDING
IMPLICIT NONE
DOUBLE PRECISION, INTENT(INOUT) :: A(:,:)
INTEGER :: M, N, LDA
EXTERNAL DLASET
M = SIZE(A,1)
N = SIZE(A,2)
CALL DLASET( "All", M, N, 1.0D0, 1.0D0, A(1,1) , M)
END SUBROUTINE
这将在A的第一列中提供20个而不是10个和10个零。
答案 0 :(得分:1)
恐怕您的现代化工作将导致彻底的困惑。如果您确实必须使用切片来调用INITMATRIX
,请不要使用F90wrapper,否则您将一无所获。当然不是按照您的计划,那是非法的。
会发生什么
DOUBLE PRECISION, INTENT(INOUT) :: A(:,:)
CALL INITMATRIX(M, N, A, LDA)
当A不连续时,编译器将复制A
并传递该副本。因此,尝试使用原始数组的描述符将毫无用处。即使它确实起作用,您最终得到的代码也会比原始代码差。
我建议或者对INITMATRIX
本身进行现代化,或者只是按照到目前为止的方式直接调用它。
还有其他选项,例如仅传递第一个元素,然后传递步幅信息(通常在带有子数组数据类型的MPI中进行),但我不建议这样做。原来看起来更好。
CALL INITMATRIX(M, N, A(1,1), LDA)
如果您实际上是在INITMATRIX_F90
中进行此操作的,则应将其放入第一个INITMATRIX_F90
示例中以明确说明。)
在新示例中,您所做的是获得每一列的前几个元素的地址的差异,实际上有时是这样做的。您可以做到,它应该可以工作。如果您使用1.使用通用扩展名LOC
(以及可选的SIZEOF
)或2.使用transfer()
来获取整数值而不是I / O例程,则会更容易。请注意,一个8字节的整数就足够了,最好使用INTEGER(C_INTPTR_T)
(或ptrdiff)。
在修正了问题POINTER
并删除了不必要的内容后,请考虑您的MWE:
PROGRAM MAIN
INTERFACE
SUBROUTINE INITMATRIX_F90(A)
DOUBLE PRECISION, INTENT(INOUT) :: A(:,:)
END SUBROUTINE
END INTERFACE
DOUBLE PRECISION :: A(20,20)
A = 1.0D0
print *,"loc in main",loc(A(1,1))
CALL INITMATRIX_F90(A(1:10,1:10))
END PROGRAM MAIN
SUBROUTINE INITMATRIX_F90( A )
IMPLICIT NONE
DOUBLE PRECISION, INTENT(INOUT) :: A(:,:)
print *,"LOC in INITMATRIX_F90:",loc(A(1,1))
call external(A)
END SUBROUTINE
subroutine external(A)
double precision :: A(*)
print *,"LOC in external:", loc(A(1))
end subroutine
输出:
> ./a.out
loc in main 140721998532864
LOC in INITMATRIX_F90: 140721998532864
LOC in external: 37291664
如您所见,当将A
传递给外部过程时,编译器会制作副本。