【问题标题】:Allocating memory in C for a Fortran allocatable在 C 中为 Fortran 可分配内存分配内存
【发布时间】:2014-05-27 14:09:52
【问题描述】:

我们正在尝试接管 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

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;

运行 ./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 的边界。有什么变通方法吗?

【问题讨论】:

  • 假定形状数组定义为你的INTEGER A(*) 总是从 1 开始,这与它们的分配无关。这里适用的是参数传递规则。
  • 但是,请理解你在这里玩火,我会使用指针,而不是可分配的。
  • 玩火是对的 :) 此外,带有指针指定的类型成员也是嵌入在类型中的 fortran 描述符,但具有不同的标志,并且末尾没有维度记录(因为它 将指向完全定义值的东西。
  • 我不这么认为。当您在您的整个数组操作之一中遇到具有自动左侧重新分配功能的编译器时,这将开始变得有趣。调试愉快。
  • 无论如何,字符串都会发生这种情况,而且一点也不好笑 :) 我查看了 fortran start-of-subroutine 汇编的大量行,以找出为什么会发生很多非常奇怪的事情。

标签: c++ fortran mixed-code allocatable-array


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    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),可与void *互操作,void *也来自iso_c_binding

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

    【讨论】:

    • @Vladirmir F:只是学术问题-您是否有可靠的方法将外部创建的描述符分配给 fortran 函数/子例程参数,尤其是对于多维数组?这将解决在 fortran 代码中知道数组维度的问题。
    • 我不明白。如果您假设大小数组,如上所述,则不使用描述符,仅使用指针。如果您假设形状数组,则传递描述符。如果你想重写指针或可分配数组的描述符,我什至不知道有可能以标准方式获取描述符的地址,但是一旦你有了它,你就可以编辑它,但是所有的赌注都没有了.
    • 我指的是后一种情况并且没有 iso_c_binding,当 C 端进行描述符创建、初始化、数据内存分配时,fortran 端将其“视为”多个常规 fortran 数组未在 fortran 函数参数声明中指定任何尺寸的尺寸。我设法用 ifort 做到了这一点,但是 不得不使用 hack 将描述符复制到堆栈上的 fortran 描述符。目标是通过多态函数接口运行 fortran 代码,这些接口采用不同的数组形状和大小,但都具有相同的数据类型。这不受官方支持。
    • 使用预处理器和通用接口要好得多。如果您不使用新的 TS 和假定形状数组的官方标题,则您自己。我真的不明白这些实验的意义所在,作为 Fortran 强项的可移植性已经完全消失了。
    【解决方案3】:

    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 - 谢谢!

    【讨论】:

    • 是的,但有些标志被标记为“内部”,而它们不是。此外,没有简单的方法可以将 fortran 变量分配给外部传递的描述符。 Intel 建议改用 iso_c_binding。
    • 不是ifort的iso_c_binding,是Fortran标准!唉,这里有一个完整的标签stackoverflow.com/questions/tagged/fortran-iso-c-binding
    • 至于第一条评论你是对的,这就是为什么我在发布后几秒钟就删除了它。
    • 好的,同意,我只研究过 ifort 接口,从来没有因为任何其他原因使用过 fortran,所以如果它是一个通用标准,那就更好了!
    猜你喜欢
    • 1970-01-01
    • 2015-06-09
    • 1970-01-01
    • 2019-12-27
    • 2018-08-12
    • 2015-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多