概要
配列を形状引継配列として手続き(サブルーチンまたは関数)に渡したときに,配列の上下限を維持する方法をまとめました.
環境
- gfortran 8.4.0
- intel fortran 2021.1
- nvfortran 20.11-0
Fortranの配列の上下限
Fortranでは,配列の上下限を指定できます.下の例では,配列にallocatable
属性を付けて動的に割り付けられるようにし,allocate
文で割り付けています.このとき上下限は,第1次元を2から7まで,第2次元を3から5までとしています.
program test
use, intrinsic :: iso_fortran_env
implicit none
integer(int32) :: l1
!! lower bound of the 1st dimension
integer(int32) :: l2
!! lower bound of the 2nd dimension
integer(int32) :: u1
!! upper bound of the 1st dimension
integer(int32) :: u2
!! upper bound of the 2nd dimension
real(real64), allocatable :: f(:, :)
character(*), parameter :: fmt = '(*(g0:,","))'
l1 = 2; l2 = 3
u1 = 7; u2 = 5
allocate (f(l1:u1, l2:u2))
print '(A)', "print bounds of f in main routine"
print fmt, lbound(f), ubound(f)
print '(A)', "---"
end program test
lbound
は配列の下限を返す組込関数,ubound
は配列の上限を返す組込関数です.これを実行すると,下記のような出力が得られます.
print bounds of f in main routine
2,3,7,5
---
手続きに配列を渡す方法
Fortranでは,手続きの仮引数として配列を渡す場合,4通りの書き方ができます.
- 形状明示配列
- 形状無指定配列
- 形状引継配列
- 大きさ引継配列
下のサブルーチンを例に,違いを説明します.
program test
! 省略
contains
subroutine print_bounds(array)
use, intrinsic :: iso_fortran_env
implicit none
real(real64), intent(in) :: array(?) ! ?の書き方によって挙動が変わる
print fmt, lbound(array), ubound(array)
end subroutine print_bounds
end program test
形状明示配列
形状明示配列は,仮引数として渡される配列の全ての上下限を明記します.
real(real64), intent(in) :: array(2:7,3:5)
program test
! 省略
l1 = 2; l2 = 3
u1 = 7; u2 = 5
allocate (f(l1:u1, l2:u2))
print '(A)', "print bounds of f as array in subroutine"
call print_bounds(f)
print '(A)', "---"
end program test
実行すると,次のような表示が得られます.
print bounds of f as array in subroutine
2,3,7,5
---
明記しているので,メインルーチン内と同じ結果が得られます.しかし,これでは柔軟性に欠けるので,上下限を別途引数として渡すことも行われています.
program test
! 省略
l1 = 2; l2 = 3
u1 = 7; u2 = 5
allocate (f(l1:u1, l2:u2))
print '(A)', "print bounds of f as array in subroutine"
call print_bounds(f,lbound(f),ubound(f))
print '(A)', "---"
contains
subroutine print_bounds(array, lower, upper)
use, intrinsic :: iso_fortran_env
implicit none
integer(int32),intent(in) :: lower(2)
integer(int32),intent(in) :: upper(2)
real(real64), intent(in) :: array(lower(1):upper(1),lower(2):upper(2))
print fmt, lbound(array), ubound(array)
end subroutine print_bounds
end program test
実行結果は同じです.
print bounds of f as array in subroutine
2,3,7,5
---
形状無指定配列
形状明示配列は,配列の上下限を指定する必要があり,そのための情報も渡す必要がありました.これは面倒なので,配列の次元だけを指定する書き方がFortran 90から追加されました.
real(real64), intent(in) :: array(:,:)
これを形状無指定配列と呼びます.形状無指定配列は,配列の上下限の情報を持っていないので,下限が1になり,上限は下限に合わせてシフトされます.
program test
! 省略
l1 = 2; l2 = 3
u1 = 7; u2 = 5
allocate (f(l1:u1, l2:u2))
print '(A)', "print bounds of f as array in subroutine"
call print_bounds(f)
print '(A)', "---"
contains
subroutine print_bounds(array)
use, intrinsic :: iso_fortran_env
implicit none
real(real64), intent(in) :: array(:,:)
print fmt, lbound(array), ubound(array)
end subroutine print_bounds
end program test
print bounds of f as array in subroutine
1,1,6,3
---
第1次元は2~7が1~6に,第2次元は,3~5が1~3になっています.
形状引継配列
形状引継配列は,形状明示配列と形状無指定配列の中間で,配列の下限の情報だけを指定します.
real(real64), intent(in) :: array(2:,3:)
実用上は,形状明示配列と同様に,配列下限も引数として渡すことで,任意サイズの配列に対応します.
program test
! 省略
l1 = 2; l2 = 3
u1 = 7; u2 = 5
allocate (f(l1:u1, l2:u2))
print '(A)', "print bounds of f as array in subroutine"
call print_bounds(f,lbound(f))
print '(A)', "---"
contains
subroutine print_bounds(array, lower)
use, intrinsic :: iso_fortran_env
implicit none
integer(int32),intent(in) :: lower(2)
real(real64), intent(in) :: array(lower(1):,lower(2):)
print fmt, lbound(array), ubound(array)
end subroutine print_bounds
end program test
print bounds of f as array in subroutine
2,3,7,5
---
大きさ引継配列
大きさ引継配列は,配列の大きさ(要素数)を受け取ります.配列の上下限どころか,次元が異なっていても問題ありません.大きさ引継配列は,最も大きい次元の要素数を*
とします.それ以外の次元では,要素数か上下限を指定する必要があります.
大きさ引継配列としては,下記の書き方が許されます.
real(real64), intent(in) :: array(2:7,*) ! 下限を指定し,最も大きい次元(第2次元)の要素数を*とする
real(real64), intent(in) :: array(1:6,*) ! :6とは書けず,下限が1でも指定が必要.あるいはarray(6,*)と書く.
real(real64), intent(in) :: array(*) ! 次元数が異なっていてもよい
real(real64), intent(in) :: array(2,3,*) ! 次元数が異なっていてもよい
形状明示配列を利用する際,下限は1と仮定されますが,上限は明記する必要があります.
program test
! 省略
l1 = 2; l2 = 3
u1 = 7; u2 = 5
allocate (f(l1:u1, l2:u2))
print '(A)', "print bounds of f as array in subroutine"
call print_bounds(f)
print '(A)', "---"
contains
subroutine print_bounds(array)
use, intrinsic :: iso_fortran_env
implicit none
real(real64), intent(in) :: array(2:7,*)
print fmt, lbound(array), ubound(array(:,:5)) ! ubound(array)やubound(array(:,:))とはできない
end subroutine print_bounds
end program test
上記のプログラムでは,lbound
では下限が1とされので配列名array
を渡すだけでよいのですが,上限を参照するubound
を利用する際は,上限を指定しなければなりません.また,gfortran 8.4.0ではコンパイルできません.
print bounds of f as array in subroutine
2,1,6,5
---
結局どうするの?
形状無指定配列が最も使いやすいのですが,配列の下限が1になってしまうのが唯一の欠点です.配列下限が1の場合は,問答無用で形状無指定配列を使いましょう.
配列下限が1になってしまう問題を回避するために,配列を成分に持つ派生型を定義し,派生型を使うこともよく行われます.これは値オブジェクトの概念も関係してくるので,今後の主流になるかも知れません.
次点で形状明示配列です.下限を引数として渡す場合,lbound
を利用すると,配列の各次元の下限値をまとめて配列として返してくれるので,それを引数として手続きに渡すのが楽です.
形状無指定配列で上下限を維持する方法
結論から言うと,配列の仮引数にpointer
属性を付け,実引数となる配列にtarget
属性を付けると,手続き内でも上下限が維持されます.
検証できるように,プログラム全体を示しておきます.
program test
use, intrinsic :: iso_fortran_env
implicit none
integer(int32) :: l1
!! lower bound of the 1st dimension
integer(int32) :: l2
!! lower bound of the 2nd dimension
integer(int32) :: u1
!! upper bound of the 1st dimension
integer(int32) :: u2
!! upper bound of the 2nd dimension
real(real64), allocatable, target :: f(:, :)
character(*), parameter :: fmt = '(*(g0:,","))'
l1 = 2; l2 = 3
u1 = 7; u2 = 5
allocate (f(l1:u1, l2:u2))
print '(A)', "print bounds of f in main routine"
print fmt, lbound(f), ubound(f)
print '(A)', "---"
print '(A)', "print bounds of f as array in subroutine"
call print_bounds(f)
print '(A)', "---"
print '(A)', "print bounds of f as pointer in subroutine "
call print_pointer_bounds(f)
print '(A)', "---"
contains
!| receive an array and print its bounds
subroutine print_bounds(array)
use, intrinsic :: iso_fortran_env
implicit none
real(real64), intent(in) :: array(:,:)
print fmt, lbound(array), ubound(array)
end subroutine print_bounds
!| receive an array as a pointer and print its bounds
subroutine print_pointer_bounds(array)
use, intrinsic :: iso_fortran_env
implicit none
real(real64), dimension(:, :), pointer, contiguous, intent(in) :: array
print fmt, lbound(array), ubound(array)
end subroutine print_pointer_bounds
end program test
pointer
属性の付け方について補足しておくと,Fortranにはポインタの配列がないので,ral(real64)
型の2次元配列へのポインタであることを明記するために,real(real64), dimension(:, :), pointer
と書いています.real(real64), pointer, contiguous, intent(in) :: array(:, :)
と書くと,ポインタの2次元配列と勘違いしてしまいそうなので,意識的に書き方を変えています1.
これを実行すると,以下のように表示されます.
print bounds of f in main routine
2,3,7,5
---
print bounds of f as array in subroutine
1,1,6,3
---
print bounds of f as pointer in subroutine
2,3,7,5
---
print_bounds
内での表示結果を見ると,配列の下限が1になっています.一方,print_pointer_bounds
内での表示結果を見ると,配列の下限が維持されていることが判ります.
まとめ
配列を手続きに渡すときに,形状無指定配列でも上下限を維持する方法を紹介しました.
手続きの定義において,仮引数の配列にpointer
属性を付け,実引数となる配列にtarget
属性を付けるだけです.
しかし,これは万能な方法ではありません.全ての配列にtarget
属性を付けるのも億劫ですし,派生型の成分にはtarget
属性を付与することができません.また,Fortranではpointer
属性が非常に曖昧で,仮引数にpointer
属性が付いていても非ポインタ変数を渡せますし,逆に仮引数にpointer
属性が付いていなくてもポインタ変数を渡せます.元々ポインタを意識しなくてよかった言語だけに,ポインタが関係すると動作がややこしくなっているように感じます.このあたりはFORTRAN 77との兼ね合いもあるので,根本的な解決は難しいのでしょうか.