はじめに
Fortran 90以降にはモジュール機能があります。
モジュールを使うと、名前空間を分けたり、同じことを二度書く手間が省けたり、それによってミスが起きにくくなります。
COMMON文、ENTRY文、外部手続きなどを使う代わりにモジュールで書きましょう!1
この記事ではFortran 2008で追加されたサブモジュールという機能については触れません。
文法
モジュール側
MODULE モジュール名
[ 宣言部 ]
CONTAINS
[ モジュールサブプログラム ] ...
END [ MODULE [ モジュール名 ] ]
モジュールの宣言部には、他のメインプログラムや手続きの宣言部と同様に、変数や型などの宣言や他のモジュールを参照するUSE文などが書けます。
モジュールサブプログラムは、モジュールに属するサブルーチンまたは関数です。
モジュールを使う側は、この変数、型、サブルーチン、関数などを使えるようになります。
また、モジュールの宣言部にはPUBLIC文、PRIVATE文が記述できます。
module m
private ! モジュール内のすべての要素をprivateに
integer, public :: hoge ! hogeはpublic
integer :: fuga ! fugaはprivate
end module m
privateな要素はモジュールを使う側からは参照できないので、モジュールの内部変数や内部関数のような使い方ができます。
使う側
規格の文法そのままだとかなりややこしいので、簡略化した文法を紹介します。
USE モジュール名 [, renameリスト]
or
USE モジュール名, ONLY: [ onlyリスト ]
renameリストは、モジュール内の要素を別の名前で使いたいときに指定します。
例えばモジュールm
にある変数hoge
をh
, fuga
をf
という名前で使いたいときは、以下のように書きます。
use m, h=>hoge, f=>fuga
onlyリストはモジュールの一部の要素だけ使いたいときに指定します。
先程のモジュールm
のうちhogeだけ使いたいときは以下のように書きます。
use m, only: hoge
onlyリストでもh=>hoge
のように別名をつけることができます。
使い方
変数、関数、サブルーチンの使い方を説明します。
モジュールには派生型も定義でき、モジュールで定義する場合には派生型に型束縛手続きが定義できるという重要な機能がありますが、どちらかというとモジュールの話というよりは派生型の話なのでここでは触れないことにします。
変数
モジュールで宣言した変数は、暗黙のうちにSAVE属性(変数がstatic領域に割り付けられる)を持ちます。普通のプログラムで変数に宣言できる属性は大抵モジュールでも宣言できます。
それらに加え、モジュール内ではPUBLIC属性、PRIVATE属性、PROTECTED属性、BIND(C)属性を持つ変数を宣言できます。
PUBLIC属性とPRIVATE属性は先程説明したとおりなので、残りの2つを紹介します。
PROTECTED属性
PROTECTED属性を持つ変数は、宣言されたモジュール内でのみ値を変更できます。
モジュールの外では値を参照することはできますが、変数定義のコンテキスト2に書いたり、ポインタの結合先に指定することはできません。
module m
integer, protected, target :: hoge
end moudle m
program main
use m, only: hoge
integer, pointer :: p
print *,hoge ! o 値は参照できる
hoge=1 ! x 代入文には書けない
p=>hoge ! x target属性を持っていてもポインタの結合先
call s(hoge) ! x サブルーチンsの対応する仮引数aがINTENT(OUT)属性を持っているので、実際には変更していなくてもコンパイルエラー
contains
subroutine s(a)
integer,intent(out) :: a
end subroutine s
end program main
BIND(C)属性
モジュールの変数にBIND(C)属性をつけると、C言語のプログラムからアクセス可能になります。
あまりいい例を思いつきませんが、下記のようにFortranで定義した変数をCでexternすることで参照できます。
Fortran側:
module m
use iso_c_binding
integer(c_int), bind(c) :: fortran_var=0
end module m
program main
use iso_c_binding
use m
interface
integer(c_int) function c_function() bind(c)
import c_int
end function c_function
end interface
fortran_var = 10
print *,c_function()
end program main
C側:
int c_function () {
extern int fortran_var;
return fortran_var;
}
モジュール内の変数を、モジュール外で新たに属性を追加することは基本的にはできません。3
COMMON文との比較
冒頭でCOMMON文を使わずにモジュールを使いましょうと言いましたので、比較のためにモジュールで宣言した変数を使う簡単な例を紹介します。
下記のsub1とsub2はuse m
という文があるので、同一の変数hoge
を参照できます。
module m
integer :: hoge
end module m
subroutine sub1
use m
! 処理
end subroutine sub1
subroutine sub2
use m
! 処理
end subroutine sub2
同じことをCOMMON文でやろうとすると以下になります。
subroutine sub1
common /com1/ hoge
integer :: hoge
!...
end subroutine sub1
subroutine sub2
common /com1/ hoge
integer :: hoge
!...
end subroutine sub2
変数hoge
の型宣言が冗長なだけでなく、コンパイラはCOMMON文で宣言された共通ブロックの構成が違ってもエラーを出してくれません。(同一ファイル内ならチェックしてくれるコンパイラはあるかも知れませんが、ファイルが分かれると流石にチェックしないと思います。)
subroutine sub1
common /com/ hoge
integer :: hoge
!...
end subroutine sub1
subroutine sub2
common /com/ hoge
real :: hoge ! sub1のhogeと型が違うのにエラーにならない
!...
end subroutine sub2
関数、サブルーチン
変数と同様に、関数とサブルーチンにもPUBLIC属性とPRIVATE属性を指定できます。
また、モジュールで宣言した手続きの特徴として、引用仕様を書かなくて良いという特徴があります。
module m
contains
subroutine sub(a)
integer, intent(inout), pointer :: a
! 処理の中身
end subroutine sub
end module m
program main
use m
integer, pointer :: a
call sub(a)
end program main
同じことを外部手続きでやると以下になります。
subroutine sub(a)
integer, intent(inout), pointer :: a
! 処理の中身
end subroutine sub
program main
interface
subroutine sub(a)
integer, intent(inout), pointer :: a
end subroutine sub
end interface
integer, pointer :: a
call sub(a)
end program main
サブルーチンsub
の情報を、すべての呼び出し側に書かないといけません。sub
の引数が多かったり属性が複雑だとすごいことになります。
問題はそれだけではなく、呼び出し側と呼び出される側が別ファイルにある場合、コンパイラによるチェックができなくなって間違った宣言があっても通してしまいます。
外部手続きの代わりにモジュール手続きを使えばコンパイラがエラーを見つけてくれるので安心ですね。
また、モジュール手続きはメインプログラムや外部手続き同様、内部手続きを持つこともできます。
この内部手続きは、同じモジュールの他の手続きからも見えません。
module m
!...
contains
subroutine sub
!...
contains
subroutine subsub
!...
end subroutine subsub
end subroutine sub
end module m
モジュールの中間情報ファイル
コンパイルの仕組みが少々変わっているので解説します。手元の確認用の環境はgfortranとflangですが、昔少し触ったintelコンパイラも大まかには同様だったように記憶しています。4
module counter
integer :: val=0
contains
subroutine increment
val = val + 1
end subroutine increment
integer function get_count()
get_count = val
end function get_count
end module counter
main.f90:
program main
use counter
call increment
call increment
print *, get_count()
end program main
m.f90だけコンパイルすると、m.o
の他にm.mod
というファイルが生成されます。(Fortran規格ではファイル名や拡張子、格納場所など何も指定していないので、コンパイラによっては多少変わるかもしれません)
m.o
は普通のオブジェクトファイルですが、m.mod
はモジュールのコンパイル時に必要な情報が格納されています。
コンパイラはmain.f90
のuse文の処理時にソースファイルであるm.f90
ではなくm
というモジュールの情報ファイルを探しに行き、なければコンパイルエラーになります。例えばgfortranは、m.mod
が無い状態で下記のように先にmain.f90
をコンパイラに渡すとエラーになります。
$ gfortran main.f90 m.f90
main.f90:2:6:
use m
1
Fatal Error: Can't open module file ‘m.mod’ for reading at (1): そのようなファイルやディレクトリはありません
compilation terminated.
下記のように先にm.f90をコンパイルすればOKです。
$ gfortran -c m.f90
$ gfortran main.f90 m.o
もしくは引数の順番を変えてもコンパイルできます。
$ gfortran m.f90 main.f90
このモジュールの中間情報ファイルはベンダごとに独自の形式で保存しており、gfortranの出力はまともに読むことができません。
straceでモジュールの中間情報ファイルのopenを見張ったり、モジュールの内容を変えたときにファイルサイズがどう変化するか見てみたところ、以下がわかりました。
- あるモジュールm1内で別のモジュールm2をuseすると、m2の要素もm1.modに書き出される
- m2のprivateな要素はm1.modに書き出されない
- m2をuseする際にonlyの指定があると、指定された要素だけm1.modに書き出される
一方でflangの出力は、以下のように普通のテキストになっています。
counter.mod:
V33 :0x24 counter
11 counter.f90 S624 0
12/09/2018 00:36:24
enduse
S 624 24 0 0 0 8 1 0 5015 10005 0 A 0 0 0 0 B 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 counter
S 625 6 4 0 0 6 1 624 5023 80000c 0 A 0 0 0 0 B 0 2 0 0 0 0 0 0 0 0 0 0 626 0 0 0 0 0 0 0 0 0 0 624 0 0 0 0 val
S 626 11 0 0 0 8 1 624 5027 40800000 805000 A 0 0 0 0 B 0 3 0 0 0 4 0 0 625 625 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 _counter$8
S 627 23 5 0 0 0 628 624 5038 0 0 A 0 0 0 0 B 0 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 increment
S 628 14 5 0 0 0 1 627 5038 0 400000 A 0 0 0 0 B 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 624 0 0 0 0 increment
F 628 0
S 629 23 5 0 0 6 630 624 5048 4 0 A 0 0 0 0 B 0 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 get_count
S 630 14 5 0 0 6 1 629 5048 4 400000 A 0 0 0 0 B 0 0 0 0 0 0 0 3 0 0 0 631 0 0 0 0 0 0 0 0 0 7 0 624 0 0 0 0 get_count
F 630 0
S 631 1 3 0 0 6 1 629 5048 4 1003000 A 0 0 0 0 B 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 get_count
Z
Z
別のモジュールをコンパイルしてみると、他にもgfortranとは異なる点がありました。
- あるモジュールm1内で別のモジュールm2をuseしても、m2の要素はm1.modに書き出されない
- m1をuse文に指定すると、m1.modとm2.modの両方のファイルをopenする
gfortranに比べ、flang方式だとファイルサイズが減りますがファイルのopenが多くなるので、コンパイル時間は長くなりそうです。
モジュールの階層構造が複雑になってくると目に見えてコンパイル時間に差が出るのではないかと思います。
各コンパイラがどんな実装でモジュールを扱っているか覗いてみるのも面白そうですね。
-
ENTRY文を使う人はそういないと思いますが。 ↩
-
代入文やサブルーチンのintent(out)の引数以外にも実はたくさんありますが、そういう場所にモジュールの変数をわざわざ指定することはあまりないと思うので気にしなくても大丈夫です。 ↩
-
例外としてVOLATILE属性とASYNCHRONOUS属性はモジュール外で追加できます。特定の区間だけvolatileにしたい場合向けと思われます。 ↩
-
もし全然違う実装のコンパイラがあれば、気になるので是非教えてください!
以下のようにモジュールを記述したファイルm.f90
とそれを使うファイルmain.f90
があったとします。
counter.f90: ↩