为Fortran可分配程序在C中分配内存



我们正试图在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_ptrtype(c_ptr),可与同样来自iso_c_bindingvoid *互操作。

---编辑---一旦我看到@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"-谢谢!

最新更新