Fortran2003の便利?なreallocate
integer,allocatable :: x(:) x = (/ 1,2,3,4,5 /) !自動的にallocate x(:) = (/ (i,i=1,10) /) !右辺は1~10の長さ10の配列だが、左辺配列長は5のまま x = (/(i,i=1,10)/) !ここでxに対するallocateが再び実行され、長さが変わる
詳しくは、NAGのドキュメントにあります。
http://www.nag-j.co.jp/fortran/fortran2003/Fortran2003_3_7.html
ちょっとまて、これを基にした次のプログラム
program hoge implicit none integer,allocatable :: x(:) integer :: i x = (/1,2,3,4,5/) write(*,*) x x(:) = (/ (i,i=1,10) /) write(*,*) x x = (/ (i,i=1,10) /) write(*,*) x stop end program hoge
1 2 3 4 5 1 2 3 4 5 *** glibc detected *** ./a.out: realloc(): invalid next size: 0x0000000001e8af80 *** (中略、GCC4.6くらいからデフォルトになったbacktrace出力) Program received signal SIGABRT: Process abort signal. (略)
とセグフォでもないエラーになるんですが・・・・・GCC4.9はまだ実装が不十分なのかな。
まあそれはともかく、NAGのこのドキュメントの後半にdeferred length characterという、実質的に可変長な文字列について触れられています。ちょっと遊んでみます。
とりあえず標準入力に適当な文字列を入力すると、その次数をカウントし出力、それを繰り返す。
"q"だと終了するようにします。
文字列をxに保存するとして、メインループは
do while (x /= "q") read(*,*) x write(*,*) len(x),x !字数、文字列の順 enddo
こんな感じです。これを文法的に整えつつ、プログラムを書きます。
ふと、このdeferred length characterを関数と受け渡しするとなるとどうするんだろう?ということなので、readとwriteをそれぞれ関数に分けてやります。
すると次のことが試せます。
標準入力からの文字列を受け取るreadStr関数。関数の戻り値に可変長文字列を指定できるか?
文字列を受け取って、出力するwritestrサブルーチン。可変長文字列として受け取れるか?
program hoge implicit none character(:),allocatable :: x do while (x /= "q") x = readStr() call writestr(x) !この2行はcall writestr(readStr())と1行で書けるんですが、while条件文にも使いたかったので。 !ちなみにcall writestr(readStr())としても別にメモリリークは起こりませんでした。 !おそらく関数引数の一時オブジェクトは勝手にdeallocateされる模様。 end do deallocate(x) !readStrの中でallocateされているため。 write(*,*) "Stop" stop contains function readStr() result(str) character(:),allocatable :: str allocate(character(len=256)::str) !allocateしておかないとreadに渡せない。allocateの例を兼ねて。 read(*,*) str str = trim(str) !ここでreallocateが起こっている。 end function readStr subroutine writestr(str) character(len=*) :: str write(*,'(3a,i0)')"string '",str,"' has length ",len(str) end subroutine writestr end program hoge
なおこのプログラム、GCC4.6ではコンパイルはできますが実行したとたんにSegmentation fault、GCC4.9だと大丈夫だと確認しました。
コンパイルして試してみましょう。
$ ./a.out ああ^~こころがぴょんぴょんするんじゃあ^~ string 'ああ^~こころがぴょんぴょんするんじゃあ^~' has length 66 hogehoge string 'hogehoge' has length 8 hello world! string 'hello' has length 5 abc string 'abc' has length 3 q string 'q' has length 1 Stop
文字列長がちゃんと変わっていないと、Fortranの仕様では余った字数のところにスペースを埋めることになってるので、引用符''の中に空白ができるはずです。たとえば5字のところにabcとだけ入れてあると、'abc 'という出力になります。
そうならず、ちゃんと後ろの空白なしで引用符が付けられているので、文字列長がちゃんと変わっていることが分かります。
あと、「hello world!」→「hello」というように空白混じりの文字列が空白以降無視されているのは、trim関数のせいです。Fortranは自然言語の取り扱いを想定していないと思われます。
ちなみにこのプログラムをvalgrind上で動かすと、初回読み込み時に
・Conditional jump or move depends on uninitialised value(s) が4つ
・Use of uninitialised value of size 8 が1つ
警告されます。まだまだExperimentalな機能なのでしょうか。
あとご想像のとおり、reallocateはパフォーマンスが落ちるようです。C++のvectorが時々重くなる感じ? そういう時はオプション-fno-realloc-lhsを付けるべし、てありました。
僕はまだ試してないので何とも言えませんが、ちょっと前のコードを新しいコンパイラでビルドするときは気をつけたほうがいいかもしれません。
楽しいFortranライフを。