1

従来の Fortran コード (+100,000 行のコード) のメモリ割り当てを C++ で引き継ごうとしています。これは、クラスタ上の分散メモリの分割と割り当てに C ライブラリを使用しているためです。割り当て可能な変数は、モジュールで定義されます。これらのモジュールを使用するサブルーチンを呼び出すと、インデックスが間違っているように見えます (1 つシフトされます)。ただし、同じ引数を別のサブルーチンに渡すと、期待どおりの結果が得られます。次の簡単な例は、この問題を示しています。

こんにちは.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(1) ではなく A(0) から始まるようです。これはおそらく、Fortran で ALLOCATE が直接呼び出されていないため、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 配列の内部表現は、C/C++ で使用されるものとは大きく異なります。

Fortran は、配列データへのポインターで始まり、要素の型のサイズ、次元数、いくつかのパディング バイト、ポインター、ターゲット、割り当て可能などのさまざまなフラグを示す内部 32/64 ビット バイト シーケンスが続く記述子を使用し、割り当てを解除できます。など。これらのフラグのほとんどは文書化されておらず (少なくとも私が使用した場合)、最後に一連のレコードがあり、それぞれが対応する次元の要素数、要素間の距離などを記述しています。

Fortran から外部で作成された配列を「見る」には、C/C++ でそのような記述子を作成する必要がありますが、Fortran は各サブルーチンのスタートアップ コードでそれらのコピーを作成してから、 'in'、'out'、'inout'、および fortran 配列宣言で使用されるその他の標識に応じて、ステートメントの最初の 1 つ。

特定のサイズで宣言された型内の配列は、同じ型と要素数の対応する C struct メンバーに (ここでも ifort で) 適切にマップされますが、ポインターと割り当て可能な型のメンバーは、実際には正しい値に初期化する必要がある型の記述子です。すべてのフィールドで、fortran が割り当て可能な値を「見る」ことができるようにします。Fortran コンパイラは、最適化のために文書化されていない方法で配列のコピー コードを生成することがありますが、そのためには関連するすべての Fortran コードを「参照」する必要があるため、これは巧妙で危険です。Fortran ドメインの外に出てくるものはすべて不明であり、予期しない動作を引き起こす可能性があります。

最善の策は、gfortran が iso_c_binding のようなものをサポートしているかどうかを確認し、そのようなインターフェイスを fortran コードに定義してから、iso_c_binding 組み込み関数を使用して C_PTR ポインターを fortran ポインターの型、配列などにマップすることです。

char の 1 次元配列とそのサイズへのポインターを渡すこともできます。これは、サイズが最後の引数として値で渡される限り、ほとんどの場合文字列に対して機能します (これもコンパイラとコンパイラ フラグに依存します)。

お役に立てれば。

編集:ウラジミールのコメントの後に「ifortのiso_c_binding」を「iso_c_binding」に変更しました-ありがとう!

于 2014-05-27T14:23:27.510 に答える