結論
- 複数回呼ばれるsubroutineやfunctionではSAVE属性ではない変数は宣言時の初期化をしてはいけない
どうして変数の宣言時に初期化をしてはいけないか
- 変数の宣言時に初期化をすると、その変数は自動的にSAVE属性になるため(お節介機能……)
- このStackOverflowの回答 を参照のこと
SAVE属性
- SAVE属性を付けた変数に起こることはibm XL FortranのSAVE属性の記事に書いてある
- ibmの記事は少々難しい用語を用いて説明されているが、誤解を恐れず要約すると
SAVE属性がついた変数は、一度変数を宣言したルーチンから抜けても
次に同じルーチンが走ったとき前回の値が保持された状態になるという特徴がある - しかも変数の宣言時の初期化処理は最初の1回しか行われない
なのでSAVE属性にするつもりが無い変数で宣言時代入をすると
2回目以降の実行の結果が意図しないものになる!!!
検証
- gfortran 9.3.0
- ファイル : main.f90 , sub.f90
- mainから10回subを呼んでsubで定義した数値 save_integer をインクリメントするプログラム
プログラム
main.f90
program main
implicit none
integer :: i
do i = 1, 10
call sub(i)
end do
end program main
sub.f90
subroutine sub(i)
implicit none
integer,intent(in) :: i
integer :: save_integer = 0 ! この初期化処理は最初の1回しか実行されない!!
save_integer = save_integer + 1
print *, save_integer," main: ", i
end subroutine sub
結果
shell
gfortran main.f90 sub.f90 -o a.out && ./a.out
1 main: 1
2 main: 2
3 main: 3
4 main: 4
5 main: 5
6 main: 6
7 main: 7
8 main: 8
9 main: 9
10 main: 10
上の結果からsave_integerにはSAVE属性を付けていないのに
integer :: save_integer = 0 が最初の1回しか実行されていないことがわかる
解決策
- 変数の宣言とは別に初期値の代入を行う
sub.f90
subroutine sub(i)
implicit none
integer,intent(in) :: i
! integer :: save_integer = 0 ! 宣言時に0を代入するのではなく……
integer :: save_integer ! 宣言時は変数の宣言だけを行って
save_integer = 0 ! 代入は別に行う!!!
save_integer = save_integer + 1
print *, save_integer," main: ", i
end subroutine sub
このように書くと……
結果
shell
gfortran main.f90 sub.f90 -o a.out && ./a.out
1 main: 1
1 main: 2
1 main: 3
1 main: 4
1 main: 5
1 main: 6
1 main: 7
1 main: 8
1 main: 9
1 main: 10
subroutineが呼ばれるごとにsave_integer = 0 の処理が走るようになった!
思ったこと
- Fortran以外の言語を触っている人ほど、宣言時代入をナチュラルに行ってバグらせそう
- SAVE属性使いたいなら明示的にinteger,save :: i = 0 とするんだから宣言時代入でなぜ自動的にSAVE属性にするのか?コレガワカラナイ