LoginSignup
9
1

More than 1 year has passed since last update.

Fortran で変数の番地を得る

Last updated at Posted at 2022-12-02

本記事では、Fortran で変数の記憶領域 (memory) 上の番地 (address) を得る関数を、非標準のものと Fortran 2003 規格に準拠した標準のものの二通り紹介します。また、それを適用した例として、1. 変数の確保の違いの確認、2. 非標準の CRAY POINTER の利用の二つの例を示します。

番地 (address) とは

電子計算機 (electronic computer) は、1 次元に並んだ bit 列の記憶領域を持っています。一般的にプログラムはこの bit 列に初期状態を与え、時間発展させ、ある時間ののち終状態を得ます。その終状態の bit 列の一部分が求めた結果になっています。

普通 bit 列は 8 bit 毎に束ねられて byte と呼ばれる単位をつくります。一般的に記憶領域は 0 番から始まる 1 次元の byte 列として順序づけられた整数座標を与えられます。この座標値を番地と呼びならわします。

Fortran プログラムから記憶領域に作用する時は、変数というものを宣言し計算機が記憶領域上に変数を割りあてるに任せ、直接に番地を指定することはなく変数名を用いて事をすませます。

しかし、特殊な状況下では変数の番地が必要になることがあります。例えば C 言語と相互利用をする場合、C 言語は番地を露わにいじることが多いので変数の番地の受け渡しを求められたりします。 そのため Fortran には変数の番地を求める関数が用意されています。

変数以外でも subroutine/function のような副プログラムのおかれた番地を求める命令もあります。

番地を得る関数

Fortran では C 言語との相互利用が強化された Fortran 2003 から、標準の関数として変数の番地が得られるようになりました。しかしながら、それ以前から非標準の関数が広く普及して使われておりました。実は非標準の関数の方が使いやすいので、両方紹介します。

非標準関数 loc()

loc 関数は、非標準ながら普通の組み込み関数になっているので、標準の関数と同じように準備なしで呼び出せます。引数として任意の変数や定数を与えると、その番地が整数値として返されます。loc は location の略号であろうと思われます。

ここで OS が 32bit か 64 bit かで返される整数値の型が 32bit 長か 64bit 長か変化してきます。32bit は 4x10**9 すなわち 4Gbyte までしか番地を表現できません(符号付整数とみると 1bit 減って 2Gbyte まで)。この点に若干注意が必要です。

プログラム例 1

変数 x に割り当てられた番地を表示します。

    program loc1 
        implicit none
        real :: x
        x = 1.2345
        print *, 'address of x is',  loc(x)
    end program loc1

実行例 1

 address of x is         1021109533848

実行ごとにアドレスの値は変化します。

標準関数 c_loc()

Fortran 2003 の C 言語との相互利用のための intrinsic module iso_c_binding 中に c_loc 関数があり、これが変数の番地を返してくれます。

引数としては変数名を与えればいいのですが、その変数は target 属性を持っていなければなりません。また、返り値は整数型ではなく cptr 型の type になっているので、そのままでは値を表示できません。

しかし中身の bit 列としては普通の整数型と同じなので transfer 関数によって bit 列を整数型として解釈しなおせば用を足せます。ただここで記憶領域の番地が 32bit か 64bit かを指定する必要が出てくるので、Fortran 2008 で導入された intrinsic module iso_fortran_env 中の int32 ないし int64 の定義を適宜使い分けることにします。transfer 関数の第二引数に与えた 0_int64 により 64bit 整数としての解釈をさせています。

このように標準の c_loc 関数は使い方が面倒なので、ちょっとした利用のためには非標準の loc 関数を用いる方が便利です。

プログラム例 2

変数 x に割り当てられた番地を表示します。

    program loc2
        implicit none
        real, target :: x
        x = 1.2345
        
        block
            use, intrinsic :: iso_c_binding
            use, intrinsic :: iso_fortran_env
            print *, 'address of x is', transfer(c_loc(x), 0_int64) 
        end block    
            
        print *, 'address by loc ', loc(x) 

    end program loc2

実行例 2

c_loc と loc で同じ結果になっています。

 address of x is       140700588818568
 address by loc        140700588818568

番地の適用例

次にこの番地を用いた例を二つほど示します。

1 static, stack, heap 変数の別

先に述べたように Fortran で用いられる変数は、計算機が記憶領域上の適当な番地に割り当てられるわけですが、変数の持つ性質によって主に 3 つに分けられた領域に割り当てられます。(処理系によっては別に定数用の実行時に読み取りのみで書き込み禁止の記憶領域への割り当てもあります。)

変数の性質の 3 つの違いとは、コンパイル&リンク時に静的に確保され実行中常に存在している静的変数 (static variable)、実行時にプログラムによって stack に自動的に確保/解放される stack variable、実行時に明示的に heap に確保/解放される heap variable の別です。

fortran では heap に動的に確保される allocatable 変数の自動割付・自動解放がだんだん範囲を広げており、自動変数 (automatic variable) の stack と heap の差が小さくなってきているので、上記の定義は境界があいまいになってきています。

以下の実例で、これら 3 種の変数の番地の違いを見てみることにします。

プログラム例 3

fortran の変数は static 属性をつけると静的に確保されます。

また初期値を与えた場合も暗黙裡に static 属性となります。これは多分 FORTRAN77 で初期値を DATA 文で与えた時の挙動を引き継いだのではないかと思われます。

Fortran 2008 で導入された block 構文は局所スコープ (local scope) をもち、その内部で特殊な属性無しに宣言された変数は、block を抜けるまで一時的に stack に確保されます。

Fortran 2003 以降、スカラー変数も allocatable 属性を持つことが可能になりました。また明示的に allocate しなくても代入により自動的に割付け/再割り付けされるようになりました。allocatable 変数は代入時に deep copy されるのですが、Fortran 95 で導入された組み込み subroutine の move_alloc を用いることにより番地の付け替えによる move semantics による代入も可能です。

Fortran 95 から subroutine/function のように局所スコープをもつ領域を抜け出すときに、その局所スコープ内部で宣言された allocatable 変数は自動解放されます。block から出る時も自動解放されます。したがって allocatable 変数も明示的に解放する必要性はかなり減っています。

    program prog3 
        implicit none
        real, static :: x_static = 1.234, y_static = 5.678
        
        block
            real :: x_stack, y_stack
            real, allocatable :: x_heap, y_heap
            x_stack = 111.111
            y_stack = 222.222
            
            x_heap = 333.333
            y_heap = 444.444
            
            
            print *, 'static', loc(x_static), loc(y_static)
            print *, 'stack ', loc(x_stack) , loc(y_stack)
            print *, 'heap  ', loc(x_heap)  , loc(y_heap)
            
            call move_alloc(x_heap, y_heap)
            print *, 'after move_alloc'
            print *, 'heap  ', loc(x_heap)  , loc(y_heap)
            
        end block    
    end program prog3

実行例 3

変数の性質により、記憶領域上の番地が大きく分かれていることが見て取れます。

call move_alloc(x_heap, y_heap) により y_heap の番地が元の x_heap の番地につけ変わり、x_heap は解放されていることがわかります。

 static       140697880477700       140697880477696
 stack          1054155471044         1054155471032
 heap           2871809245296         2871809246400
 after move_alloc
 heap                       0         2871809245296

2 CRAY POINTER の利用

Cray pointer とは、かつての Cray 社の FORTRAN にあった非標準の拡張で、その後ほかの多くの処理系でも使えるようになりました。Fortran 90 の pointer よりは C 言語の pointer に近く、既存の変数の番地を cray pointer に代入することで変数に作用できます。番地そのものをいじることも可能です。

Cray Pointer は最近見かけなくなりましたが、古いプログラムで使われていたりします。

プログラム例 4

Cray pointer は pointer(pointer, pointee) の様に宣言します。pointer の対象となる pointee 変数はあらかじめ宣言しておきます。

配列 x の番地を loc 関数によって得て、これを pointer に与えます。すると pointee 変数が配列 x の第一要素 x(1) の別名となります。pointer を配列の各要素の byte 数だけずらすことで x(2), x(3)... に対応するようずらせます。

配列 x は単精度実数で宣言したので pointer は 4byte づつずらせばよいのですが、Fortran 2008 から導入された組み込み関数 storage_size を用いて配列 x の要素の大きさを求めました。 storage_size の返り値は bit 単位なので 8 で割って byte 数に変換しています。

これとは別にやはり Fortran 2008 から導入された iso_c_binding 中の c_sizeof 関数を用いても実現できます。こちらは byte 数を返すので 8 で割らなくてもいいのですが、配列全体としての大きさを返すので配列サイズで割る必要があります。 

    program prog4
        implicit none
        real :: pointee, x(5)
        pointer(ipointer, pointee) 
        integer :: i
        
        x = [1.0, 2.0, 3.0, 4.0, 5.0]
        
        ipointer = loc(x)
        
        do i = 1, 5
            print *, pointee 
            ipointer = ipointer + storage_size(x) / 8 ! bit to byte
        end do    
        
    end program prog4

実行例 4

Cray Pointer を配列 x の番地に合わせ、各要素ごとの byte 数分ずらすことで配列 x の中身を順に書かせています。

   1.000000
   2.000000
   3.000000
   4.000000
   5.000000

以前は FORTRAN は pointer が無いから駄目なんだ!とか散々罵られており Fortran 90 で pointer が規格に入った後も、Fortran の pointer は制限がきつすぎて本当の pointer では無いとか散々な言われようでした。

しかし最近では、pointer もすっかり有害視されるようになり、pointer のないことが素晴らしいこととして謳われるようになり、スマート・ポインタと呼ばれる Fortran 90 の pointer なみに制限のきつい pointer が喜ばれるような世の中になりました。

まとめ

以上、Fortran で変数の番地を得る方法を示し、その適用例として変数の性質の違いで番地が大きく違っていることを見、また Cray Pointer での利用法も見てみました。

なお処理系としては Intel Fortran と gfortran のふたつで動くことを確かめました。

9
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
1