如何将C ++中的2D数组传递给Fortran子例程?

时间:2010-11-03 01:19:45

标签: c++ fortran

我正在编写一个小型C ++程序,它将一个二维数组(复数)传递给一个Fortran子程序,然后将其接收回来填充值。我写了一个通过并接收一维数组的版本,这很有效。 2-D版本不起作用(我的真正目标是编写具有大尺寸的4-D版本 - 因此必须动态分配这些数组)。

我将发布我的工作代码和非工作代码,但首先请注意,我被迫使用结构(简单的,只包含两个双精度),因为Fortran似乎以与其自己的原生完全相同的方式解释这些复数。这就是我的1-D版本有效的原因。出于同样的原因,我认为这不是一个“复杂数字”的问题。

这是我的工作代码。将一维复数数组传递给Fortran子程序:

Fortran子程序:

subroutine carray(A)
complex*16 A(2)

A(1) = cmplx(3,7)
A(2) = cmplx(9,5)

return
end

C ++代码:

include <iostream>
include <complex>
using namespace std;

struct cpx{double r, i;};

extern"C"
{
   void carray_(struct cpx* A);
}

int main()
{
   struct cpx* A;
   A = new struct cpx [2];

   carray_(A);

   complex<double>* P;
   P = new complex<double> [2];

   for(x = 0; x < 2; x++)
   {
      real(P[x] = A[x].r;
      imag(P[x] = A[x].i;
   }

   cout << real(P[0]) << "\t" << imag(P[0]) << "\n";
   cout << real(P[1]) << "\t" << imag(P[1]) << "\n";

   return 0;
}

使用以下命令进行编译无需投诉:

gfortran -c CarrayF.f
g++ -c CarrayC.cc
g++ -o Carray CarrayC.o CarrayF.o -lgfortran

因此,只要我将(本机)Fortran复数视为两个双精度的结构,我就可以将它们放入(非本机)C ++复杂类型中。 Fortran子程序似乎非常高兴收到一个指向它需要一个数组的指针。到目前为止一切都很好。

这是我传递2D数组的非工作尝试:

Fortran代码:

subroutine carray(A)
complex*16 A(2,2)

A(1,1) = cmplx(3,7)
A(1,2) = cmplx(9,5)
A(2,1) = cmplx(2,3)
A(2,2) = cmplx(4,9)

return
end

C ++代码:

include <iostream>
include <complex>
using namespace std;

struct cpx{double r, i;};

extern"C"
{
   void carray_(struct cpx** A);
}

int main()
{
   struct cpx** A;
   A = new struct cpx* [2];
   for(int x = 0; x < 2; x++)
   {
      A[x] = new struct cpx [2];
   }

   carray_(A);

   complex<double>** P;
   P = new complex<double>* [2];
   for(int x = 0; x < 2; x++)
   {
      P[x] = new complex<double> [2];
   }

   for(x = 0; x < 2; x++)
   {
      for(int y = 0; y < 2; y++)
      {
         real(P[x][y] = A[x][y].r;
         imag(P[x][y] = A[x][y].i;
      }
   }

   cout << real(P[0][0]) << "\t" << imag(P[0][0]) << "\n";
   cout << real(P[0][1]) << "\t" << imag(P[0][1]) << "\n";
   cout << real(P[1][0]) << "\t" << imag(P[1][0]) << "\n";
   cout << real(P[1][1]) << "\t" << imag(P[1][1]) << "\n";

   return 0;
}

这实际上编译没有抱怨(与1-D版本相同的编译过程),但运行可执行文件会产生立即的分段错误。由于一起使用两种语言的头痛,调试器没有帮助。

我在某个地方犯了一个小错误吗?我似乎没有超出任何数组范围。 Fortran子程序很高兴收到一个指针,但显然它不知道如何处理指针指针。通常,Fortran只会处理数组名称,即使是多维数组,但我需要了解Fortran如何处理2D数组。

4 个答案:

答案 0 :(得分:8)

在这个时代这样做的方法是使用ISO C Binding。正式这是Fortran 2003的一部分,但得到了Fortran编译器的广泛支持,包括gfortran。它甚至包括复杂类型的互操作性!请参阅“内部模块”一章的“ISO_C_Binding”一节中的gfortran手册中的类型列表。另外,“混合语言编程”一章中有几个例子。在C ++方面,使用extern C来调用例程,以便在没有名称修改的情况下使用C调用约定。在Fortran端,使用ISO C绑定。然后,您将在语言级别具有兼容性,而不必对编译器的工作方式做出假设。

答案 1 :(得分:6)

Fortran中的多维数组在内存中存储为平面列主要数组。它们是不是指针的指针 - 它们实际上只是一维数组,你必须做一些额外的算术来计算数组索引。

从C ++传递数组的正确方法是这样的:

extern "C" void carray_(struct cpx *A);
...
struct cpx A[2*2];
carray_(A);

complex<double> P[2][2];
for(int i = 0; i < 2; i++)
{
    for(int j = 0; j < 2; j++)
    {
        // note order of indicies: A is column-major
        P[i][j] = std::complex<double>(A[j*2 + i].r, A[j*2 + i].i);
    }
}

答案 2 :(得分:2)

新建议

c ++中的二维数组 - 我重复 - 与指针{*}的指针相同!

当你这样做时

struct cpx** A;

你正在设置构建一个所谓的“参差不齐的数组”,其中没有一个fortran等价物。你想要像

这样的东西
struct cpx *A[2][2] = new struct cpx[2][2];

是指向行2长的二维数组的指针。

{*}是的,您可以使用二维数组表示法访问指针指针结构,但它们在内存中的布局不同。 :: grumble :: 告诉其他人阵列和指针在c中相同的人需要遇到Big Foam Clue Bat。

  • 二维数组是<row-dimension>*<column-dimension>*sizeof(<type>)的单个分配。它的名称衰减到指向类型的指针。
  • 一个不规则的数组是1+分配。一个是<column-dimension<*sizeof(<type>*),另一个是<row-dimension>*sizeof(<type>)

陈旧,正确但不适用的建议

这里需要注意的是c ++和fortran相信数组在内存中以的方式存储

C ++认为a[1][1]之后的记忆位置是a[1][2],而fortran认为那是a[2][1]。区别在于行主要(c,c ++等)和列主要(fortran,matlab,其他几个)。

请注意,默认情况下,这与fortran索引数组分开。

答案 3 :(得分:2)

我将跳过复杂类型的不必要的复杂性。把它插回去只是一个微不足道的改变。我还将尺寸更改为 3x4,因为 2x2 会使事情变得混乱。

静态数组

如果数组是静态的,您只需将维度 [3][4] 交换为 (4,3)。了解如何使用现代 Fortran 与 C 的绑定:

subroutine carray(array) bind(C, name="carray")
  use iso_c_binding, only: c_double
  implicit none
  real(c_double), intent(in) :: array(4,3)
  integer :: j
  
  do j = 1, 3
    print '(*(f8.2))', array(:,j)
  end do
      
end subroutine
extern "C"
{
   void carray(double arr[3][4]);
}

int main()
{
   double array[3][4];
   
   for(int i = 0; i < 3; i++)
   {
      for(int j = 0; j < 4; j++)
      {
         array[i][j] = i + j/100.;
      }
   }

   carray(array);

   return 0;
}
> g++ -Wall carray.f90 main.cc -lgfortran
> ./a.out 
    0.00    0.01    0.02    0.03
    1.00    1.01    1.02    1.03
    2.00    2.01    2.02    2.03

动态数组

在这种情况下,您确实想使用 contiguous 2D array 而不是锯齿状数组。只是一大块内存,而不是分散在许多内存位置的行。如果您想使用 [][] 对其进行索引,请参阅刚刚给出的链接。我将展示手动(或使用宏)在二维中索引的一维数组的简单情况。同样,扩展只是机械的,几乎是微不足道的:

subroutine carray(array, n, m) bind(C, name="carray")
  use iso_c_binding, only: c_double, c_int
  implicit none
  integer(c_int), value :: n, m
  real(c_double), intent(in) :: array(m, n)
  integer :: j
  
  do j = 1, n
    print '(*(f8.2))', array(:,j)
  end do
      
end subroutine
extern "C"
{
   void carray(double* arr, int n, int m);
}

int main()
{
   int n = 3;
   int m = 4;
   double* array = new double[n*m];
   
   for(int i = 0; i < n; i++)
   {
      for(int j = 0; j < m; j++)
      {
         array[j+i*m] = i + j/100.;
      }
   }

   carray(array, n, m);

   return 0;
}
> g++ -Wall carray.f90 main.cc -lgfortran
> ./a.out 
    0.00    0.01    0.02    0.03
    1.00    1.01    1.02    1.03
    2.00    2.01    2.02    2.03

请注意 Fortran 中的 value 属性。如果您不想要它或不能将它放在那里,则必须在 C 或 C++ 中传递地址(int*&n)。