23
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

FortranAdvent Calendar 2018

Day 9

Fortranのモジュール機能

Last updated at Posted at 2018-12-09

はじめに

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にある変数hogeh, fugafという名前で使いたいときは、以下のように書きます。

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が多くなるので、コンパイル時間は長くなりそうです。
モジュールの階層構造が複雑になってくると目に見えてコンパイル時間に差が出るのではないかと思います。
各コンパイラがどんな実装でモジュールを扱っているか覗いてみるのも面白そうですね。

  1. ENTRY文を使う人はそういないと思いますが。

  2. 代入文やサブルーチンのintent(out)の引数以外にも実はたくさんありますが、そういう場所にモジュールの変数をわざわざ指定することはあまりないと思うので気にしなくても大丈夫です。

  3. 例外としてVOLATILE属性とASYNCHRONOUS属性はモジュール外で追加できます。特定の区間だけvolatileにしたい場合向けと思われます。

  4. もし全然違う実装のコンパイラがあれば、気になるので是非教えてください!
    以下のようにモジュールを記述したファイルm.f90とそれを使うファイルmain.f90があったとします。
    counter.f90:

23
13
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?