我们正试图在C++中接管遗留Fortran代码(+100000行代码(的内存分配,因为我们正在使用C库对集群上的分布式内存进行分区和分配。可分配变量是在模块中定义的。当我们调用使用这些模块的子例程时,索引似乎是错误的(偏移了一(。然而,如果我们将相同的参数传递给另一个子例程,我们就会得到我们期望的结果。下面的简单示例说明了这个问题:
你好。f95:
MODULE MYMOD
IMPLICIT NONE
INTEGER, ALLOCATABLE, DIMENSION(:) :: A
SAVE
END MODULE
SUBROUTINE TEST(A)
IMPLICIT NONE
INTEGER A(*)
PRINT *,"A(1): ",A(1)
PRINT *,"A(2): ",A(2)
END
SUBROUTINE HELLO()
USE MYMOD
IMPLICIT NONE
PRINT *,"A(1): ",A(1)
PRINT *,"A(2): ",A(2)
CALL TEST(A)
end SUBROUTINE HELLO
main.cpp
extern "C" int* __mymod_MOD_a; // Name depends on compiler
extern "C" void hello_(); // Name depends on compiler
int main(int args, char** argv)
{
__mymod_MOD_a = new int[10];
for(int i=0; i<10; ++i) __mymod_MOD_a[i] = i;
hello_();
return 0;
}
我们正在使用进行编译
gfortran -c hello.f95; c++ -c main.cpp; c++ main.o hello.o -o main -lgfortran;
运行输出/主要是
A(1): 1
A(2): 2
A(1): 0
A(2): 1
正如您所看到的,A的输出是不同的,尽管两个子例程都打印了A(1(和A(2(。因此,HELLO似乎从A(0(开始,而不是从A(1(开始。这可能是因为ALLOCATE从未在Fortran中直接调用过,因此它不知道A的边界。有什么解决办法吗?
ISO_C_BINDING"等效"代码:
c++代码:
extern "C" int size;
extern "C" int* c_a;
extern "C" void hello();
int main(int args, char** argv)
{
size = 10;
c_a = new int[size];
for(int i=0; i<size; ++i) c_a[i] = i;
hello();
return 0;
}
fortran代码:
MODULE MYMOD
USE, INTRINSIC :: ISO_C_BINDING
IMPLICIT NONE
INTEGER, BIND(C) :: SIZE
TYPE (C_PTR), BIND(C) :: C_A
INTEGER(C_INT), POINTER :: A(:)
SAVE
END MODULE
SUBROUTINE TEST(A)
IMPLICIT NONE
INTEGER A(*)
PRINT *,"A(1): ",A(1)
PRINT *,"A(2): ",A(2)
END
SUBROUTINE HELLO() BIND(C)
USE, INTRINSIC :: ISO_C_BINDING
USE MYMOD
IMPLICIT NONE
CALL C_F_POINTER(C_A,A,(/SIZE/))
PRINT *,"A(1): ",A(1)
PRINT *,"A(2): ",A(2)
CALL TEST(A)
END SUBROUTINE
输出:
A(1): 0
A(2): 1
A(1): 0
A(2): 1
Fortran数组伪参数始终从子程序中定义的下限开始。它们的下限在调用期间不会保留。因此,TEST()
中的自变量A
将始终从1开始。如果您希望它从42
开始,您必须执行以下操作:
INTEGER A(42:*)
关于分配,你在玩火。为此,使用Fortran指针要好得多。
integer, pointer :: A(:)
然后,您可以通过将数组设置为指向C缓冲区
use iso_c_binding
call c_f_pointer(c_ptr, a, [the dimensions of the array])
其中c_ptr
为type(c_ptr)
,可与同样来自iso_c_binding
的void *
互操作。
---编辑---一旦我看到@Max la Cour Christensen实现了我上面概述的内容,我就发现我误解了您代码的输出。描述符确实错了,尽管我没有写任何明显的错误。上述解决方案仍然适用。
fortran数组的内部表示与C/C++中使用的内部表示非常不同。
Fortran使用的描述符以指向数组数据的指针开始,然后是元素类型大小、维数、一些填充字节、内部32/64位字节序列,该序列指示各种标志,如指针、目标、可分配、可释放等。这些标志中的大多数都没有文档记录(至少在我使用过的ifort中是这样(,最后是一系列记录,每个描述对应维度中元素的数量、元素之间的距离等
要从fortran"查看"外部创建的数组,您需要在C/C++中创建这样的描述符,但它并没有到此为止,因为fortran还会在每个子例程的启动代码中复制它们,然后才能到达您的第一个语句,这取决于fortran数组声明中使用的"in"、"out"、"inout"等指示符和其他指示符。
用特定大小声明的类型中的数组可以很好地映射(同样在ifort中(到相同类型和元素数量的相应C结构成员,但指针和可分配类型成员实际上是类型中的描述符,需要在其所有字段中初始化为正确的值,以便fortran可以"看到"可分配的值。这充其量是棘手和危险的,因为为了优化目的,fortran编译器可能会以未记录的方式为数组生成复制代码,但它需要"查看"所有涉及的fortran代码。任何来自fortran域的内容都是未知的,可能会导致意外行为。
你最好的办法是看看gfortran是否支持iso_c_binding之类的东西,并为你的fortran代码定义这样的接口,然后使用iso_c_bbinding内部函数将c_PTR指针映射到fortran指针到类型、数组等。
您还可以传递一个指向一维char数组的指针及其大小,只要大小是通过作为最后一个参数的值传递的(同样,编译器和编译器标志相关(,这种方法就适用于字符串。
希望这能有所帮助。
编辑:在Vladimir的评论后,将"ifort的iso_c_binding"更改为"iso_c_bbinding"-谢谢!