Fortranは古い言語で,オブジェクト指向プログラミングには向かないが,数値計算のプログラミングには最も楽な言語なので,基本的にはFortranを使用している.常にFortranに接しているとは言え,忘れっぽいのでメモを残す.
マイ・コーディング・ルール
implicit noneを用いる
いちいち変数を宣言するのが面倒と言う人もいるが,結局はデバッグ作業の時間が短くなる. おそらく,バグの数はコードの長さのベキで増えると思われ,大規模なプログラムになればなるほどデバッグの作業時間が大半を占めるようになる.故に,変数宣言を書くことを怠って,デバッグの作業時間が増えるのはアホらしい.コーディングの時間を減らしたいのであれば,emacsなどのような高度なエディタを使って効率化が可能.
moduleの使用を推奨する
moduleを用いることで,変数名や副プログラム名の範囲をmodule内に制限することができるのが便利. 例えば,分子動力学で様々なポテンシャルのルーチンをmoduleとして書いておけば,それぞれのmodule内で同じ名前(たとえばget_rhoなど)のルーチンを定義することができる. しかし,副プログラムがあるmoduleに依存するようにプログラムを組むと,その副プログラムの移植性は低下するので,汎用性を考えると,あまりmoduleに頼りすぎない方が良いかもしれない.
subroutine内における配列の宣言は極力allocatableを用いる
f77の場合には,work配列をプログラムの先頭で宣言して,それをsubroutineに渡す必要があったが, f90以降は動的配列が許されるようになったので,その機能を使う. allocatableとせずに,subroutine内で配列を宣言するとスタック・オーバーフローで実行時エラーとなることがあり,このエラーは非常にバグ取りが難しい. ただし,頻繁にcallされるroutine内でallocate, deallocateをすると計算負荷が大きくなるので注意する.
intent文を用いる
subroutineに渡す引き数の入力・出力がすぐ分かるようになるので大事. かつ,コンパイラでエラーを検知してくれるようになる. また,subroutine呼び出し時やsubroutineからの復帰時に無駄なコピーを無くすことができるので,計算が速くなる.頻繁にcallされるsubroutineではintent(in)やintent(out)を正確に記述するべし.
配列の代入式は省略しすぎない
arr(1:n)= 1d0
arr(:)= 1d0
arr= 1d0
などは同じ作業だが,最後の表記などは一見配列には見えないので視認性が悪い.
TIPS
Intel fortranのオプション
- -ip: inliningを行うか否か.単一のファイル内のみに適用.
- -ipo: inliningを行うか否か.ファイル間に適用.いくつかのファイルに多くの小さな関数がある場合にはこれはquite powerfulらしい.
- -xP, -axP: vectorization switch.-xPはすべてのループをvectorizeし,-axPはvectorizeとnon-vectorizeの2つのコード・ブランチを生成する.ループの範囲が決まっていないようなループでは有効だろう.大きなループではvectorizeした方が効率的だが,小さなループではnon-vectorizeの方が効率的.とりあえず-axPの方を選んでおけば安全? 以下の意味があるらしい. **-xW:Pentium 4 用 **-xN:Pentium 4 + SSE2 用 **-xP:Pentium 4 + SSE2 or Core Duo 用 **-xT:Core 2 Duo 用
- -parallel: 複数コアが存在するときに,最外ループを並列化.
- -ftz: ごく小さい値をゼロにする?
- -tpp7: 推奨しない.-mtuneに代替されたらしい.何するか不明. 要約すると, $ ifort -O3 -ftz -ip -ipo -parallel -axP がお薦めのようだ.
GNU Fortranのオプション
ココを参照.
- -ffpe-trap=list:
list
には以下があるらしい.- invalid (invalid floating point operation, such as
sqrt(-1.0)
) - zero (divided by zero)
- overflow (overflow in a floating point operation)
- underflow (underflow in a floating point operation)-
- precision (loss of precision during operation)
- denormal (operation produced a denormal denormal value)
- invalid (invalid floating point operation, such as
デバッグの際に有用なオプション
-fbacktrace
: 何行目で止まったかを出力-fbounds-check
: 配列参照関連の出力-fpe0
: ゼロ割あんどの不正な演算の出力-fcheck=all
: 上記を全部? ←とりあえずこれをつけてデバッグするべし!
allocatable変数のsave
allocatable変数をsaveすることは問題ないらしい.
subroutine sub(n)
implicit none
real(8),allocatable,save:: a
logical,save:: lfirst=.true.
if(lfirst) then
allocate(a(n))
lfirst=.false.
endif
end subroutine sub
として,サブルーチンが最初に呼ばれた時のみallocateするようにしておくこと.
**注)**これに関連して注意しなければならにことがある.*「サブルーチンの中で使用される変数は,必ず初期化して使うことを心掛けること.」*さもなくば,何度も利用されるルーチンでは,前にコールされた時の値が変数に残っていることがある.
あり得ない(と思われる)バグがあるときの対処法
例えば,以下のコード
......
write(6,'(a)') " here1"
allocate(array(n))
write(6,'(a)') " here2"
......
nにはちゃんと値が入っているのに,here1からhere2へ到達しないようなことがある. このように,あり得ないと思われるバグは探すのに苦労する. こういう場合は,メモリが自分が思ったように使われていない事が多い. オプション「-check all」をつけてコンパイルしてメモリオーバーなどをチェックする必要がある.
のように,noarg_temp_created という引き数を与えておいた方がよいかも.-check オプションに何も指定しないと,「an array temporary was created」という文があまりにもたくさん出てきて煩わしい.
MPI並列プログラムの場合には,オプション「-check all」をつけても,ただsegmentation fault
とだけ出て,そのエラーの場所がwrite文の位置によって変化するような場合がある.
これは,-check all
などのシングルスレッドに関するチェックだけでは,MPI関数でのバグを検出できないためだろう.まぁ,この場合は,MPI関数へ渡している値のどこかがおかしいということなので,そこに集中すれば良いだろう.
note: MPI関数への変数受け渡しでのバグは見つけるのが困難なので,最も気をつけるべし
Moduleの宣言に関して
例として,moda.f90
が以下のようになっており,
module moda
implicit none
integer:: ia
end module moda
modb.f90
が,
odule modb
use moda
implicit none
integer:: ib
end module modb
のように,moda
を参照している場合.modb.f90
はmoda.f90
の後にコンパイルされなけらばならない.
また,これらを利用するtest.f
が,
program test
use modb
implicit none
ia=1
ib=2
write(6,'(10i8)') ia,ib
end program test
のような場合でも,modb
を介してmoda
の変数ia
が参照される.変数だけでなくfunctionやsubroutineも同様.
ゆえに,test.f
内でuse moda
は不要だが,書いても大丈夫なようだ.
プリプロセッサの利用
慣れると便利なのでメモしておく.
Fortranの場合,拡張子を大文字にするか,コンパイラ・オプションに-cpp
と付けることでプリプロセッサで処理してからコンパイルしてくれる.
ソースファイルtest.f
に,
#ifdef TEST
write(*,*) " I am Sam."
#else
write(*,*) " You are Sam."
#endif
と書いておいて,
とコンパイルすれば,
となるし,-DTEST
を指定しておけば,以下のようになる.
静的ライブラリと共有ライブラリ(動的ライブラリ)
ライブラリとはオブジェクトファイルの集合体のようなもので,静的ライブラリは「lib???.a」という名前とし,動的ライブラリは「lib???.so」とするのが規則らしい.
静的ライブラリを作成するには,ar
コマンドを使用して,
とすればよいらしい. これを用いてプログラムを作成する場合は,
とする.
ライブラリに含まれるオブジェクトファイルは -fPIC
オプションを付けてコンパイルされるべきのようだ.PICはPosition Independent Codeの略のようで,そのサブプログラムがいるアドレスからの相対アドレスで記述ようなプログラムコードとなる.ライブラリとして呼ばれるコードはそのように作られるべきとのこと.
静的ライブラリにモジュールが含まれている場合
モジュールは
use some_module
のような形式でsome_module.mod
ファイルを読み込むわけだが,これはC言語での
#include <some_module.h>
のようなものと同じで,インクルードパス内にそのファイルが存在しなければならない.
さて,静的ライブラリには一般的に,この上記の*.mod
ファイルは含まれない.
これらはsome_module.h
なんかと同じで,どこかインクルードパスが通った場所にあって読み込まれなければならない.
なので,/path/to/library/libsome.a
に入っているsome_module
モジュールを呼び込むソースファイルをコンパイルする際には,
/path/to/include/some_module.mod
のありかも以下のように指定してやる必要がある.
$ gfortran -I/path/to/include -L/path/to/library -lsome hoge.f
バイナリデータの出力
open文に,
open(ionum, file='filename', form='unformatted')
write(ionum) a(1:n)
close(ionum)
のようにすればバイナリで出力されるが,OSをまたぐとバイナリの扱い方が異なるので読めなくなる. 以下のようにすれば,OSをまたいでも読めるようになるかも...
open(ionum, file='filename', form='binary')
write(ionum) i1,a(1:n),i1
close(ionum)
ここで,i1
はa(1:n)
のバイナリのサイズ.a(:)
がreal*8ならば,i1=n*8
byteである.読み込む場合も同様で,
open(ionum, file='filename', form='binary')
read(ionum) i1,a(1:n),i1
close(ionum)
でいけるはず. 一般に,unformattedやbinaryで出力すると,ファイルサイズが下がり,出力の速度が速くなる. また,
do i=1,n
write(ionum) a(i)
enddo
とやるよりも,
write(ionum) a(1:n)
や
write(ionum) (a(i),i=1,n)
とする方が高速.ファイルの出力はコンピュータの作業の中でかなり遅い作業なので,ここを高速化することはそのプログラムを高速化するのに効果的なことが多い. また,スーパーコンピュータによってはかなり高速化するものがある.
プロファイラの使用
gprofというプロファイラを使用して,実行時にどの関数がどれだけ呼ばれ,その程度の時間を要しているかなどが分かるので,高速化を行う際には必ず最初にチェックすべし. コンパイル時(とリンク時)に,「-pg」というオプションを付けてコンパイルしたプログラムを実行すると,gmon.outというファイルが生成される.
gmon.outは付けなくても良い. こうすることで,プロファイル結果が得られる.
Getting current directory
To get current working directory in fortran, you can use getcwd() subroutine.
PROGRAM test_getcwd
CHARACTER(len=255) :: cwd
CALL getcwd(cwd)
WRITE(*,*) TRIM(cwd)
END PROGRAM
You can use this subroutine call not only with GNU compiler but also Intel Fortran compile.
引数を取る
UnixやLinuxのコマンドの様に引数を取りたい場合,command_argument_count()
関数と,getarg()
サブルーチンを用いる.
integer:: nargc
character*128:: argv
real*8:: x
nargc= command_argument_count()
do i=1,nargc
call getarg(i,argv)
read(argv,*) x
enddo
などとするとargvに引数が入る.do文で回せば全ての引数を得ることができ,read文で数値に変換することも可能.
外部コマンドの利用
call system('ls')
とかすると,「ls」コマンドが実行される. 「cd」して作業ディレクトリを変更するのはできない模様...
また,MPIで並列にsystem()関数を利用して,外部executableを実行しようとすると,
[king01.bw.nitech.ac.jp:16967] OOB: Connection to HNP lost
のようなエラーが出て,実行できないようだ...
characterとstringの違い
参考:USING STRINGS AND CHARACTER ARRAYS
character変数の宣言方法として,
character(len=10):: c1
character:: c2*10
character:: c3(10)
c1 = '0123456789'
c2 = '0123456789'
c3 = '0123456789'
write(6,'(a)') c1
write(6,'(a)') c2
write(6,'(a)') c3
この3つはおそらく,上二つは同じで長さ10の文字列だが,
下の c(10)
は長さ1のcharacterが10個連なった配列という意味で,上二つとは異なる.
出力結果は,
0123456789
0123456789
0
0
0
0
0
0
0
0
0
0
のように,c3
に関しては配列の全ての要素に0
が代入されていることが分かる.
配列c3
にしっかりと代入するには,
do i=1,3
c3(i) = c1(i:i)
enddo
のように要素ごとに代入しなければいけない.
ここで,配列であるc3
にはc3(i)
のように要素を指定できるが,stringであるc1
にはc1(i:i)
のように文字列範囲を指定しなければならない.
また,character配列を出力する際,
write(6,'(10a)') c3
or
write(6,'(10a)') c3(1:10)
とすれば,
0123456789
と改行なしで出力されるようになる.
ファイル入出力
End Of File (EOF)の検出
read文に,**end=**というstatementを加えておくと,EOFを検出したら,そこで指定した行へ飛ぶことができる.
read(10,*,end=100) c
100 write(6,*) ' Reading file finished.'
文字列のtrim
ある文字列cに,その文字列変数の長さよりも短かい文字列を代入すると,残りの文字列は空白となる.
character(len=5):: c
c= 'AB'
write(6,'(a)') c
write(6'(a,i)') 'len(c)=',len(c)
write(6'(a,i)') 'len_trim(c)=',len_trim(c)
write(6,'(a,a)') 'trim(c)=',trim(c)
こうすると,
AB
len(c)=5
len_trim(c)=2
trim(c)=AB
となる.つまり,cには’AB ‘という5文字が入っており,**len(c)**で調べると文字列長は5となり,**len_trim(c)**で調べると2文字と分かる.**trim(c)**で空白を除いた文字列を取り出せる.
スラッシュを読み込む
普通に,
read(5,*) str
として読み込んだ文字列に,スラッシュ /
が含まれていると,そこで文字列は終わってしまう.
例えば,../some_dir
を読み込もうとしても,..
のみが読み込まれたことになる.
スラッシュも読み込むためには,
read(5,'(a)') str
のように文字列をしっかりと指定して読み込む必要がある.
一行のデータ数が分からない場合
文字列データstr
からdelim
で仕切られたデータの数をカウントする関数num_data(str,delim)
を作成し,
以下のように一行のデータ数をカウントしてから再読込する.
character(len=128):: ctmp
...
read(ionum,'(a)') ctmp
nd = num_data(trim(ctmp),' ')
backspace(ionum)
read(ionum,*) (a(i),i=1,nd)
...
function num_data(str,delim)
implicit none
character(len=*),intent(in):: str
character(len=1),intent(in):: delim
integer:: num_data
integer:: i
i=1
num_data = 0
do
if( i.gt.len(str) ) exit
if( str(i:i).ne.delim ) then
num_data = num_data + 1
do
i = i + 1
if( str(i:i).eq.delim ) exit
end do
end if
i = i + 1
end do
return
end function num_data
- 一度,一行読み込んで,データ数をカウントした後,
backspace(ionum)
として一行戻っている. - 最初の読み込みは,
read(ionum,'(a)') ctmp
のように,formatをcharacterで指定して読み込まなければならない.
出力フォーマットに変数値を指定する
例えば配列arr(ndim)
の長さndim
が変化するような場合,その長さに応じて一行に出力する数を変更したいような場合,
write(6,'(1000f10.2)') arr(1:ndim)
のように,十分に大きい数値(ここでは1000)のように数を指定することもできるが,
write(cfmt,'(i10)') ndim
write(6,'('//trim(cfmt)//'f10.2)') arr(1:ndim)
のようにして,ndim
に応じた長さの出力長さを設定することができる.
ディレクトリを作る
Fortranからディレクトリを作るには,
call system('mkdir -p dirname')
とすれば良い.この「-p」をつけないと,既にそのディレクトリが存在している場合にエラーとなってしまう.
ファイル,ディレクトリの存在を確認する
inquire(file="path/to/file",exist=l_exists)
if( .not. l_exists ) then
stop ' file path/to/file does not exist !!!'
endif
のようにして,ファイルの存在を確認できる.「file=」の代わりに「directory=」にするとディレクトリの存在を調べられると書いてるところもあるが,g95ではコンパイルエラーとなる.g95でもifortでも「file=」でディレクトリの存在も調べられる?
subroutineの引数にfunction/subroutineを!
subroutineの引数にsubroutineを渡す場合のサンプルプログラムを以下に載せておく. 重要なのは,subroutine sub2を渡すためには,program testの中で,「external:: sub2」のようにsub2をexternal指定していなければいけない. また,sub2を受け取るsub1において,interface文でsubの引数などを定義しておかねばならない. このinterface中のsubの引数の定義と,実際に渡されるsub2の引数が異なっているとエラーとなる.
program test
implicit none
integer:: i
external:: sub2 !<--- external指定が必須
i=10
call sub1(sub2,i)
end program test
!=======================================================================
subroutine sub1(sub,m)
implicit none
integer,intent(inout):: m
interface ! <--- interfaceでsubの定義が必須
subroutine sub(n)
integer,intent(inout):: n
end subroutine sub
end interface
call sub2(m)
print *,' m=',m
end subroutine sub1
!=======================================================================
subroutine sub2(n)
implicit none
integer,intent(inout):: n
n=n+1
print *,' n=',n
end subroutine sub2
LAPACKの利用
実対称行列の固有値問題(dsyev)
dsyevを用いるルーチンの例.
subroutine eigval(ndim,dm,ev)
implicit none
integer,intent(in):: ndim
real(8),intent(out):: ev(ndim)
real(8),intent(inout):: dm(ndim,ndim)
integer:: lwork,info
real(8),allocatable:: work(:)
ev(1:ndim)= 0d0
lwork= 3*ndim-1
allocate(work(lwork))
call dsyev('N','U',ndim,dm,ndim,ev,work,lwork,info)
if(info.ne.0) then
write(6,'(a,i3)') " error?: info(dsyev)=",info
stop
endif
deallocate(work)
end subroutine eigval
ndim次元の行列dmの固有値をevに返す.
エルミート行列の固有値問題(zheev)
zheevを用いるルーチンの例.
subroutine eigval(ndim,d,ev)
implicit none
integer,intent(in):: ndim
real(8),intent(out):: ev(ndim)
complex(8),intent(inout):: d(ndim,ndim)
integer:: lwork,info
real(8),allocatable:: rwork(:)
complex(8),allocatable:: work(:)
ev(1:ndim)= 0d0
lwork= 2*ndim-1
allocate(work(lwork),rwork(3*ndim-2))
call zheev('N','U',ndim,d,ndim,ev,work,lwork,rwork,info)
if(info.ne.0) then
write(6,'(a,i3)') " error?: info(zheev)=",info
stop
endif
deallocate(work,rwork)
end subroutine eigval
ndim次元の行列dの固有値をevに返す.
Fortran90に関する覚え書き
Moduleの利用
Moduleは便利だが,コンパイルに関しては注意が必要. Moduleをuseするソースをコンパイルする際には,先にそのmoduleを含むソースがコンパイルされていなければならない. また,一つのソース内にmoduleとそれをuseするルーチンが存在する場合,moduleが上に書いてないといけない.
hogeというmoduleを含むソースhogehoge.fをコンパイルすると,中間ファイルとしてhoge.modというファイルが生成される.これは,このmoduleをuseするルーチンをコンパイルする際に必要な情報を提供する.故にhogeというmoduleに変更がなされた場合には,hogehoge.fをコンパイルしてから他のソースをコンパイルすべし.
任意長さの文字列をsubroutineで受け取る
文字列を引き数として受けとるsubroutineにおいて,文字列の長さを考慮するのはかなり面倒なことで,fortran90では(もしかしたらfortran77から?)この問題を簡単に解決することができる.
subroutine hoge(c)
character(len=*),intent(in):: c
write(6,'(a)') " c=",trim(c)
end subroutine hoge
などとすれば,渡された文字列cの空白を取り除いた(trimされた)部分だけが出力される. character(len=*)とすることで,任意の長さの文字列を受け取れるsubroutineとなる.
多次元配列の定数宣言?
どうも多次元配列で一次元配列と同じように定数として宣言しようとしたら怒られる. 次善策として,
real(8):: hoge(1:3,1:3)
data hoge / 1d0, 1d0, 1d0 &
1d0, 1d0, 1d0 &
1d0, 1d0, 1d0 /
のようにデータとして宣言した.
makefile関連
プログラマの理想
どのマシンであっても以下の2つのコマンドでコンパイルが完了することを目指す.
マシンのアーキテクチャ依存性を吸収したmakefileを作って以上のように簡単にコンパイルが可能であれば,ユーザにプログラムを渡した際に,上記2つのコマンドだけを指示すれば良い.
autoconf
上記理想を実現するために,アーキテクチャ依存性を吸収する方法がautoconfによって与えられている. autoconfを実行すると,configure.acとMakefile.inを利用してconfigureスクリプトが生成される. このconfigureを実行することで,マシン依存のMakefileが生成される. 故に,プログラマがユーザに渡すべきものは,ソースコード一式とautoconfによって生成されたconfigureとMakefile.inである.
実際にFortranプログラムにautoconfを導入した記録
autoscan
まずは,ソースの存在するディレクトリ上で,autoscanを実行とすると,いくつかのエラー文が出力されるがconfigure.scanというファイルが生成される.configure.scanの中身はこんな感じ.
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59)
AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)
AC_CONFIG_SRCDIR([params_eam_al.h])
AC_CONFIG_HEADER([config.h])
# Checks for programs.
AC_PROG_CC
AC_PROG_CPP
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_CONFIG_FILES([makefile])
AC_OUTPUT
configure.ac
そのconfigure.scanファイルをconfigure.acと名前変更し,configure.acファイルを編集する.
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59)
AC_INIT([adaptive-MDCG], [1.0.0], [kobayashi.ryo@nitech.ac.jp])
AC_CONFIG_SRCDIR([pp_main.f])
#AC_CONFIG_HEADER([config.h])
# Checks for programs.
AC_PROG_F77([mpif90])
AC_PROG_CPP
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_CONFIG_FILES([makefile])
AC_OUTPUT
- Fortranを使用するので,AC_PROG_F77というマクロを書いている(mpif90を指定しているが…).
- AC_CONFIG_SRCDIR()には,そのディレクトリ内にあるソースファイルを一つだけ指定(ディレクトリを特定できるものであればよい).
- AC_CONFIG_FILES()には最終的に出力されるファイル名を記述.
- 階層構造がある場合には,AC_CONFIG_FILESに,各階層のmakefileを記述し,各階層のディレクトリにmakefile.inを作成しなければならない.例えば,
AC_CONFIG_FILES([makefile src/makefile])
makefile.in
makefile.inというファイルを用意する.(automakeというコマンドでmakefile.amからmakefile.inを作成することが出来るが,高機能故に可読性が悪いので,ここでは使用しない.)
こんな感じで,@F77@のようにマシン依存するようなところ(configureによって決定してもらいたいところ)に@で挟んだキーワードを書いておく.どんなキーワードが存在するのかは良く知らない.
autoconf
としてautoconf
を実行すると,configure.acとmakefile.inからconfigureスクリプトが生成される.
コンパイル
でコンパイルできるようになる.
プリプロセッサに関して
大抵のFortranコンパイラの場合,拡張子が**.Fや.F90のように大文字になっていれば,プリプロセッサを通してからコンパイルする.なので,makefileに以下のように書いておくだけで,.fだろうが.F**だろうが関係なくコンパイルしてくれる.
.SUFFIXES: .o .f .f90 .F .F90
.f.o:
$(FC) -c $(FFLAGS) $<
.f90.o:
$(FC) -c $(FFLAGS) $<
.F.o:
$(FC) -c $(FFLAGS) $<
.F90.o:
$(FC) -c $(FFLAGS) $<
けれども,どうも**.mod**ファイルが存在するとmakeの挙動がおかしくなる.例えば,
m2c -o hoge.o hoge.mod
のようなコマンドを勝手に実行しようとしてしまい,
m2c -o hoge.o hoge.mod
make: m2c: No such file or directory
make: *** [hoge.o] Error 1
のようなエラーで止まってしまう.この問題を解決するには,
%.o : %.mod
という行をmakefileに加えれば良いらしい.
ソースコード解析
大きなプログラムのソースコードを読まなければならないこともある.もともと人が書いたソースコードを読むことが辛い作業なのに,それが大量にあると考えると,頭が痛くなる... そこで,そのプログラムの大局的な構造がどうなっているかだけでも知れれば作業がだいぶ楽になるだろう.
doxygen
doxygenは上記の要望をかなえるソフトウェア.
- doxygen: generate documentation from source code
基本的には,
$ doxygen <conf-file>
のように <conf-file>
を代入して,そのディレクトリ以下にあるソースコード群の解析を行う.
<conf-file>
は以下のようにして基本的なものを作成することができる.
$ doxygen -g <conf-file>
この中の PROJECT_NAME
くらいは変更して,コード解析するものの名前にしておいた方がよい.
デバッグ
Ref.9 に詳しいが,プリプロセッサ(CPP)を用いたデバッグテクニックを覚えておくと便利なようだ.
拡張子が.F90
のように大文字となっていると,FortranコンパイラがそのソースコードはCPPで処理されるものと判断し,CPPで処理してからコンパイルする.もしくは,-CPP
オプションをつけることでCPP処理を強制することもできる.
makefileでコンパイルする場合には,おそらくCPPFLAGS
という変数がコンパイルコマンドでオプションとして渡されていると思うので,
$ make CPPFLAGS=-D_DEBUG <program-name>
のようにして_DEBUG
を指定することができる.
_DEBUG
の指定
#ifdef _DEBUG
デバッグ作業コード
#endif
上記のように,#ifdef _DEBUG
から#endif
までが,_DEBUG
が指定されたときにのみCPPを通過してコンパイルされるコードに残る.
下記のように,デバッグとは別のコードとすることも可能.
#ifdef _DEBUG
デバッグ作業コード
#else
別のコード
#endif
DEBUG_PRINT関数の定義
以下のように,_DEBUG
を指定されたときのみ与えられた変数を出力する関数DEBUG_PRINT
を定義する.
#ifdef _DEBUG
#define DEBUG_PRINT(a) write(*,*) (a)
#else
#define DEBUG_PRINT(a)
#endif
これを用いて調べたい変数hoge
を
DEBUG_PRINT(hoge)
とすることで_DEBUG
が指定されたときににのみ変数を出力するようになる.
コンパイラオプション
-O4
などのような最適化オプションをつけるとデバッグのエラーが分かりにくくなることがあるので,-O0
として最適化レベルを下げてコンパイラが正確な情報を出してくれるようにする.-fcheck=bounds
として,メモリ範囲の外側にアクセスしようとするようなエラーをちゃんとチェックするようにする.