FrontPage  Index  Search  Changes  Login

Fortran 覚書

tips

副プログラム内での allocate は可能

  • main.f90
program main
  use alloc
  implicit none
  real(8), allocatable :: a(:)
  call sub(a)
  print *, size(a)
end program main
  • alloc.f90
module alloc
  implicit none
contains
  subroutine sub(x)
    real(8), intent(inout), allocatable :: x(:)  ! intent(in) はダメ。allocatable も必須。
    allocate (x(5))
  end subroutine sub
end module alloc

即席配列

複数の値を配列として持っていないが、配列として扱いたい(例えば複数の値を副プログラムに渡すとき、仮引数が複数の値を配列としてまとめてしか引き受けてくれない)場合、わざわざ事前に配列を宣言しておかなくてもその場で作ればよい:

program test
  use mod
  implicit none
  integer :: a = 5
  call sub( (/1,a,3/) )  !! ここ
end program test

module mod
  implicit none
contains
  subroutine sub(x)
    integer, intent(in) :: x(:)
    print *, x
  end subroutine sub
end module mod
  • 結果
$ ./a.exe 
           1           5           3

format

p 編集記述子

  • p の前の数字の桁数だけ小数点の位置を右にずらす(0.86e+01 などという無駄な一桁目を排除)。繰り返しの数はこの後に来る。通常は e 編集記述子とともに使う。f 編集記述子とともに使うと指定した桁数だけ異なった値が表示されるので注意。
write(*,'(1p3e14.6)') a,b,c
  • 注意点:一つの write 文内の編集記述子の中で一度 p 編集記述子を記述するとその後にも適応される。
write(*,'(1pe10.2,f6.2)') 0.5, 0.2

実行結果

$ ./a.out
  5.00E-01  2.00

es 編集記述子

  • e 編集記述子に 1p を前置したものに等しい。つまり、上記 1pe10.2 は es10.2 等しい(gfortran 4.5.3)。

指数部の桁数

  • 指数部の桁数が3桁以上の場合、e編集記述子で陽に桁数を指定しないと(少なくともgfortran4.6.0では)出力がおかしくなる。
real(8) :: a = 3.2d+100
write(*,'(e10.2,e10.2e3)') a,a

実行結果

$ ./a.exe 
  0.32+101 0.32E+101

この誤ったデータ出力形式(0.32+101)を読み込み側が正しく読み取ってくれるとは限らない。

getarg, iargc

  • 文字列で受け取るので、変換が必要。例:
program main
  use multigrid
  integer :: iargc, i
  character*10 :: argv(3)
  real(8) :: val(3)

  do i=1, iargc()
     call getarg(i,argv(i))
     read(argv(i),*) val(i)
  end do

  ! 以下で val(i) が使える

end program main

プロファイラ gprof

  • コンパイル時に -pg オプションを付ける
  • ./a.out
    • gmon.out というファイルが生成する
  • gprof ./a.out gmon.out

(cygwin では時間を正しく測れないらしい)

要注意

unformatted 形式を使う場合の注意点

unformatted で出力する場合、および unformatted で出力されたデータを読み出す場合に認識しておくべき点は2点

  • エンディアン
  • データ前後のマーカー(ヘッダー・フッター)の長さ

異なる環境下で出力された unformatted データを読み込む場合に意図したように読み込めない場合は上記のいずれかをチェックすると良い。

エンディアン

open 文内で convert='big_endian' などと指定することで対応可能。endian 問題は、fortran に限った話ではないので、ここでは省略。

データ前後のマーカー(ヘッダー・フッター)の長さ:-frecord-marker オプション

fortran でバイナリ出力をする時に用いられる unformatted 形式で連続したデータを出力した場合、そのデータの前後に marker と呼ばれる情報が記録される。その marker のサイズが4バイトの時と8バイトの時があるので、書き出しと読み込みを異なる環境で行う場合には注意が必要。

例えば以下のようなプログラム:

program check_marker_4_or_8
  integer :: n(32), i
  do i=1,32
     n(i) = 2**i-1
  end do
  open(10,file='unformatted.dat',form='unformatted')
  write(10) n
  close(10)
end program check_marker_4_or_8

を実行し、出力されたファイルを16進数で見ると、エンディアンはとりあえず見ないとすると、以下のいずれかになる。

  • マーカーが4バイトの場合
$ od -x unformatted.dat 
0000000 0080 0000 0001 0000 0003 0000 0007 0000
0000020 000f 0000 001f 0000 003f 0000 007f 0000
0000040 00ff 0000 01ff 0000 03ff 0000 07ff 0000
0000060 0fff 0000 1fff 0000 3fff 0000 7fff 0000
0000100 ffff 0000 ffff 0001 ffff 0003 ffff 0007
0000120 ffff 000f ffff 001f ffff 003f ffff 007f
0000140 ffff 00ff ffff 01ff ffff 03ff ffff 07ff
0000160 ffff 0fff ffff 1fff ffff 3fff ffff 7fff
0000200 ffff ffff 0080 0000
0000210
  • マーカーが8バイトの場合
$ od -x unformatted.dat 
0000000 0080 0000 0000 0000 0001 0000 0003 0000
0000020 0007 0000 000f 0000 001f 0000 003f 0000
0000040 007f 0000 00ff 0000 01ff 0000 03ff 0000
0000060 07ff 0000 0fff 0000 1fff 0000 3fff 0000
0000100 7fff 0000 ffff 0000 ffff 0001 ffff 0003
0000120 ffff 0007 ffff 000f ffff 001f ffff 003f
0000140 ffff 007f ffff 00ff ffff 01ff ffff 03ff
0000160 ffff 07ff ffff 0fff ffff 1fff ffff 3fff
0000200 ffff 7fff ffff ffff 0080 0000 0000 0000
0000220

今の場合、データの長さは4*32=128バイト(16進数で0080)なので、マーカーは

  • 4バイトの場合
0080 0000
  • 8バイトの場合
0080 0000 0000 0000

となっている。

例えば、最近のgfortran は 4 バイトがデフォルトになっているようなので、8バイトで出力されたファイルを読み込むには "-frecord-marker=4" オプションをつけるとよい。

構造体の配列のメモリと、その要素の引き渡し

構造体を要素とする配列の中の1構造体要素を配列として副プログラム渡したい場合、新たに(ユーザから分からないが)通常の配列が作られて(メモリをアロケートされて)、それが渡される。

main

01 program test
02   use sub
03   implicit none
04   type(vector), allocatable :: v(:)
05   integer :: i
06   allocate( v(4) )
07   do i=1, size(v)
08      write(*,'(3z9.8)') loc(v(i)%x), loc(v(i)%y), loc(v(i)%z)
09   end do
10   call sub1(v)
11   call sub2(v%x)
12 end program test

sub

01 module sub
02   implicit none
03   type vector
04      real(8) :: x,y,z
05   end type vector
06 contains
07   subroutine sub1(v)
08     type(vector), intent(in) :: v(:)
09     integer :: i
10     write(*,*)
11     do i=1, size(v)
12        write(*,'(3z9.8)') loc(v(i)%x), loc(v(i)%y), loc(v(i)%z)
13     end do
14   end subroutine sub1
15   subroutine sub2(v)
16     real(8), intent(in) :: v(:)
17     integer :: i
18     write(*,*)
19     do i=1, size(v)
20        write(*,'(z9.8)') loc(v(i))
21     end do
22   end subroutine sub2
23 end module sub

実行結果

012B6A60 012B6A68 012B6A70
012B6A78 012B6A80 012B6A88
012B6A90 012B6A98 012B6AA0
012B6AA8 012B6AB0 012B6AB8

012B6A60 012B6A68 012B6A70
012B6A78 012B6A80 012B6A88
012B6A90 012B6A98 012B6AA0
012B6AA8 012B6AB0 012B6AB8

012B6AF8
012B6B00
012B6B08
012B6B10

分かったこと

  • v(1)%x, v(1)%y, v(1)%z はメモリ上で連続的に配置されている。つまり v(1)%x, v(2)%x,... は連続していない
  • 配列 v の中の要素 x を配列として手続きに渡そうとする場合、それらはメモリ上で連続して配置されていないが、副プログラムとしては連続して配置されている必要があるため、新たにメモリを確保して連続的に配置しなおす必要があるが、それは fortran が自動的にやってくれている。
  • この処理にメモリの確保が発生するため、この処理を大量にする場合はかなりのオーバーヘッドが発生する。よって、「fortran はアドレス渡しなので大きな配列を受け渡ししても負担がかからない」と思っていると失敗する(した)。

character

文字列変数の配列の表記 c(i)(j:k)

配列の添え字と文字列内の文字数の順番とその記法に若干違和感を感じるが。

program character_array2
  implicit none
  integer :: i
  character(len=10) :: c(3)
  do i=1, 3
     c(i) = 'abcdefg'
     write(*,'(i3,1x,a)') i, c(i)
  end do
  do i=1, 3
     c(i)(i:i) = '+'
     write(*,'(i3,1xa)') i, c(i)
  end do
end program character_array2
  • 結果
1 abcdefg   
2 abcdefg   
3 abcdefg   
1 +bcdefg   
2 a+cdefg   
3 ab+defg   

write(*,*) と write(*,'(a)')

write(*,*) で文字列を出力すると、先頭に空白が一つ入る。

$ more write_character.f90 
program write_character
  character ch*5
  ch = '12345'
  write(*,*) ch
  write(*,'(a)') ch
end program write_character

$ gfortran write_character.f90  

$ ./a.exe 
 12345
12345

添え字が1から始まらない配列を副プログラムに渡す場合

  • サイズは引き継がれるが、lbound は明示しない限り引き継がれない。(lboundだけ伝えれば良いらしい)
program test
  implicit none
  real(8) :: x(0:20)
  write(*,'(a,i2,a,i2,a)') 'main (',lbound(x,1),':', ubound(x,1),')'
  call sub1(x)
  call sub2(x,lbound(x,1))
contains
  subroutine sub1(a)
    real(8) :: a(:)
    write(*,'(a,i2,a,i2,a)') 'sub1 (',lbound(a,1),':', ubound(a,1),')'
  end subroutine sub1
  subroutine sub2(a,l)
    real(8) :: a(l:)
    integer :: l
    write(*,'(a,i2,a,i2,a)') 'sub2 (',lbound(a,1),':', ubound(a,1),')'
  end subroutine sub2
end program test
  • 結果
$ ./a.exe 
main ( 0:20)
sub1 ( 1:21)
sub2 ( 0:20)

module のコンパイル手順

モジュールを利用するプログラムをコンパイルする時、オブジェクトファイルを作る段階(つまりリンクする前)で既にモジュールファイル(.mod)が必要。

このことから、ファイル間の整合性(モジュールに関連する部分)は、リンク時にではなく、コンパイルの段階(リンクの前)にチェックしていることが分かる(多分)。

良くあるパターンの一つとしては、モジュールも通常のオブジェクトファイルも同じディレクトリに作られる場合で、そのときはモジュールファイルも同じディレクトリにあるため、use で呼ばれているモジュールファイルをコンパイラが自分で見つけられる。このため、上記の仕組みに気づきにくい。しかし、モジュールを他のプログラムからも利用したい時(.modがコンパイル中のディレクトリにない場合)には、このことを理解しておく必要がある。

  • test_main.f90
program main
  use test_module
  implicit none
  print *, pi
end program test
  • test_mod.f90
module const
  implicit none
  real(8), parameter :: pi = 3.14159265358979
end module const
  • コンパイル例
$ ls *
test_main.f90  test_mod.f90

include:

$ g95 -c test_mod.f90 -fmod=./include

$ ls *
test_main.f90  test_mod.f90  test_mod.o

include:
const.mod

$ g95 -c test_main.f90 
In file test_main.f90:2

  use const
           1
Fatal Error: Can't open module file 'const.mod' at (1) for reading: No such file or directory

$ g95 -c test_main.f90 -I./include  ! .mod ファイルのディレクトリを指定

$ g95 test_main.o                   ! ここは test_mod.o も const.mod も不要

$ ls
a.exe*  include/  test_main.f90  test_main.o  test_mod.f90  test_mod.o

$ ./a.exe 
 3.1415927410125732
  • 試しに…
$ rm -f a.exe test_mod.o include/const.mod 

$ ls *
test_main.f90  test_main.o  test_mod.f90

include:

$ g95 test_main.o

$ ./a.exe 
 3.1415927410125732

補足

  • モジュールファイルのあるディレクトリパスの指定は、インクルードファイルのディレクトリパスを指定する要領と同じ -I でよい。
  • .mod を作る時に同時に作られる .o ファイルは、リンクに不要っぽい。

疑問

contains

疑問:contains に引数を渡す意義は?

program contains_test
  implicit none
  real(8) :: a = 3.d0, b=2.d0
  write(*,*) square(b)
contains
  real(8) function square(x)
    real(8), intent(in) :: x
    square = a*a
  end function square
end program contains_test

結果

$ ./a.exe 
   9.0000000000000000     

かといって、引数なしにはできない。

intent属性 out と inout の違いは?

  • inout と out は違うのか?
    • in は、手続き内部で値を変更していないことをコンパイラがチェックする目的があるため、その存在意義はわかる。
    • out は、手続き内で値を代入するので、手続きが呼ばれる前に値が入っていてもいなくても関係ない。その意味で、inout との違いが分からない。
    • 値のコピー(c的に)をしている? アドレスを受け渡ししているだけじゃないのか?

interface

  • 存在意義は? 重複記述を増やすだけ? module を使えば不要?

覚書

module ファイルの置き場を示すオプション

  • gfortran: -M ./include
  • g95: -fmod=./include

gfortran 環境

  • obuntu
    • apt-get で gfortran 入手可。
    • 実行時に cygwin では起こらないエラーが起きる。
  • vine
    • 手動で binary を install。しかし、libmpfr.so.1 がない、と怒られる。
  • cygwin
    • 手動でインストール。
    • プロファイラが時間を計れないみたい。
Last modified:2015/01/22 15:57:31
Keyword(s):[プログラミング]
References: