1

我们正在尝试接管 C++ 中遗留 Fortran 代码(+100,000 行代码)的内存分配,因为我们使用 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

主文件

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;

运行 ./main 的输出是

 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 的边界。有什么变通办法吗?

4

3 回答 3

3

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
于 2014-05-27T15:27:24.497 回答
1

Fortran 数组伪参数始终从子例程中定义的下限开始。在调用期间不保留它们的下限。因此,论点A总是TEST()从一开始。如果您希望它从 开始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),可以互操作的void *,这也是从哪里来的iso_c_binding

---编辑--- 一旦我看到@Max la Cour Christensen 实现了我上面勾勒的内容,我发现我误解了您的代码输出。描述符确实是错误的,尽管我没有写任何明显的错误。上面的解决方案仍然适用。

于 2014-05-27T14:48:03.143 回答
1

fortran 数组的内部表示与 C/C++ 中使用的表示非常不同。

Fortran 使用的描述符以指向数组数据的指针开头,然后是元素类型大小、维数、一些填充字节、内部 32/64 位字节序列,指示各种标志,例如指针、目标、可分配、可释放等。这些标志中的大多数都没有记录(至少在我使用过的 ifort 中),最后是一系列记录,每个记录都描述了相应维度中的元素数量、元素之间的距离等。

要从 fortran 中“查看”外部创建的数组,您需要在 C/C++ 中创建这样的描述符,但是,它并没有到此结束,因为 fortran 还在每个子例程的启动代码中复制它们,然后才能到达您的第一个语句,取决于“in”、“out”、“inout”等指标以及 fortran 数组声明中使用的其他指标。

以特定大小声明的类型中的数组可以很好地映射(再次在 ifort 中)到具有相同类型和元素数量的相应 C 结构成员,但指针和可分配类型成员实际上是需要初始化为正确值的类型中的描述符在他们的所有领域中,fortran 可以“看到”可分配的值。这充其量是棘手和危险的,因为 fortran 编译器可能会以未记录的方式为数组生成复制代码以进行优化,但它需要“查看”所有涉及的 fortran 代码才能这样做。任何来自 fortran 域的内容都是未知的,并且可能导致意外行为。

您最好的选择是查看 gfortran 是否支持 iso_c_binding 之类的东西并为您的 fortran 代码定义此类接口,然后使用 iso_c_binding 内在函数将 C_PTR 指针映射到指向类型、数组等的 fortran 指针。

您还可以将指针传递给 char 的一维数组及其大小,这主要适用于字符串,只要大小是按值作为最后一个参数传递的(同样,编译器和编译器标志依赖)。

希望这可以帮助。

编辑:在 Vladimir 发表评论后将 'ifort's iso_c_binding' 更改为 'iso_c_binding - 谢谢!

于 2014-05-27T14:23:27.510 回答