FortranとCの境界線を越えろ:ISO_C_BINDINGで実現する「メモリの共有」
こんにちは。かつて宇宙の深淵を計算で追いかけていた元アーキテクトです。
君たちが今、Fortranという「伝説の数値計算言語」に触れているのは素晴らしいことです。CやPythonで育った君たちにとって、Fortranの古いコードは「異国の暗号」に見えるかもしれません。しかし、Fortran 2003以降、この言語はC言語と「言語レベルで握手」ができるようになりました。
今回は、C言語とFortranの間で「配列を安全に受け渡す」という、現場で最も重要かつ、一歩間違えればセグメンテーションフォールトの深淵に叩き込まれるトピックについて解説します。
—
なぜ「メモリの持ち方」を知る必要があるのか?
FortranとCで最大の衝突ポイントは、「メモリ上の並び順」です。
- Fortran(列優先 / Column-major): 2次元配列 `A(i, j)` なら、`i` が変わるごとにメモリ上で隣り合います。
- C(行優先 / Row-major): 2次元配列 `A[i][j]` なら、`j` が変わるごとにメモリ上で隣り合います。
この「ルール」を理解せずにポインタを渡すと、Fortran側で「行列の転置」をしたのと同じ状態になり、計算結果が爆発します。まずはここを肝に銘じておきましょう。
—
1. C_LOCで「Fortranのメモリの住所」を教える
Fortran側で確保した配列をC言語に渡すには、`ISO_C_BINDING` モジュールを使います。まずは「メモリの住所(ポインタ)」を取得する `C_LOC` 関数です。
! 必要なモジュールをインポート
use, intrinsic :: iso_c_binding
implicit none
! Fortran側で配列を確保
real(c_double), target :: my_array(100) ! target属性が必須!これが無いと住所が確定しない
type(c_ptr) :: ptr_to_array
! C_LOCでメモリのアドレスを取得
ptr_to_array = c_loc(my_array)
ここがポイント: `target` 属性を忘れないでください。Fortranコンパイラは「ポインタで参照される可能性がある変数」に対しては、最適化の際に慎重な振る舞いをします。これがないと、最悪の場合、コンパイラが「この配列は誰も見ていないはずだ」と判断し、変数をレジスタに追いやってメモリ上の住所が消滅することがあります。
—
2. C_F_POINTERで「Cのポインタに意味を与える」
次に、C言語から受け取ったポインタをFortranで使う場合です。Cの `void` はただの「住所」であり、Fortranコンパイラはそれが何次元で、どれくらいの大きさなのかを知りません。そこで `C_F_POINTER` の出番です。
subroutine process_c_data(c_ptr_input, n)
use, intrinsic :: iso_c_binding
type(c_ptr), value :: c_ptr_input ! Cからのポインタ
integer(c_int), value :: n ! 配列のサイズ
real(c_double), pointer :: f_array(:) ! ポインタとして宣言
! C言語の住所(c_ptr_input)を、Fortranの配列(f_array)として形状を付与する
call c_f_pointer(c_ptr_input, f_array, [n])
! これで f_array(1:n) として普通に計算できる!
print , f_array(1)
end subroutine
—
3. 実戦的なビルドのヒント
現場で「うまくリンクできない!」と嘆く若手が一番最初にハマるのがリンク時のシンボル名です。Fortranコンパイラは、デフォルトで関数名の後ろにアンダースコア(`_`)を付けたり、大文字小文字を変換したりします。
これを防ぐために、`bind(c, name=”…”)` を使うのがモダンな作法です。
! C言語側から “compute_data” という名前で呼び出せるようにする
subroutine compute_data(data) bind(c, name=”compute_data”)
use, intrinsic :: iso_c_binding
! … 中身 …
end subroutine
今日のまとめ:血の滲む教訓
1. 列優先を忘れるな: 多次元配列を扱うときは、ループのインデックス順序(左側のインデックスが内側)を徹底すること。これがFortranのキャッシュ効率を最大化する鍵です。
2. `target` 属性は魔法の杖: ポインタを扱う変数は、ケチらずに `target` を付けましょう。
3. `c_f_pointer` は型とサイズを厳格に: ここで指定するサイズが間違っていると、メモリ破壊が起きます。デバッグ時に `bounds-check` フラグ(gfortranなら `-fcheck=bounds`)を有効にするのを忘れないでください。
最初は難しく感じるかもしれませんが、一度この「メモリを直接握る感覚」を掴めば、PythonからC拡張を書くよりも遥かに高速で、かつ堅牢な数値計算基盤が作れるようになります。
さあ、恐れずにコンパイラを回してみましょう。エラーメッセージは、君たちが成長するための「対話の記録」ですからね!

コメント