Fortran/メモリ管理と共通ブロック
導入と歴史的背景
[編集]Fortran90標準以前のほとんどのFortranプログラムは、構造を持たず、共有された構造化されたデータをほとんど持たずに、自己完結型のデータを使用していました。ただし、共通ブロックを使用して、構造化された方法や非構造化された方法でデータを共有することは可能でした。さらに、Fortranプログラムではほとんどメモリ管理が行われていませんでした。Fortran90以前は、割り当てられたストレージは(Crayポインタなどの)特定の拡張機能を介してさえ可能ではありませんでした。しかし、現代のFortranは、多くの現代のプログラミングパラダイムをサポートし、アロケータブルデータ(アロケータブル型を含む)の完全なサポートを提供し、ポインタの使用を可能にします。
モジュール内の共有変数
[編集]Fortran90以降、共有変数はモジュールの使用によって便利に管理されます。共通ブロックは、Fortran90標準以前にグローバルメモリを定義するために使用されました。ただし、現代のFortranでは、その使用は非推奨です。Fortranモジュールには、サブルーチンや関数も含めることができますが、これらの機能の議論は後で行います。共有変数の管理に関しては、モジュールで定義することができます:
module shared_variables implicit none private integer, public, save :: shared_integer integer, public, save :: another_shared_integer type, public :: shared_type logical :: my_logical character :: my_character end type shared_type type (shared_type), public :: shared_stuff end module shared_variables
すべてのモジュール変数に対してsave
がデフォルトであるため、それが前の値を保持するという意味で、明示的にこの設定を行うことが良いとされる場合がありますが、良い慣例とされています。その後、次のようにメインプログラムでモジュールを使用できます:
program my_example use shared_variables, only: shared_integer, shared_stuff implicit none integer :: some_local_integer ! これは動作し、shared_integerをいくつかのローカル変数に割り当てます。 shared_integer = some_local_integer ! これは、型shared_stuffのコンポーネントmy_characterを ! 標準出力に出力します。 write (*,*) shared_stuff%my_character ! しかし、これは機能しません。別の共有整数が ! モジュールからインポートされていないため、プログラムはコンパイルされません。 shared_integer = another_shared_integer end program my_example
共通ブロック
[編集]共通ブロックは、現代のFortran標準(Fortran90以降)ではモジュール内の公開変数の使用によって置き換えられていますが、古いFortran標準(77およびそれ以前)での使用のために歴史的に重要です。共通ブロックは、Fortran90以前の標準で共有された一般的なストレージを使用するFortranの方法でした。共通ブロックは、グローバルメモリを定義する方法の一つです。ただし、注意が必要です。ほとんどの言語では、共通メモリ内の各項目は個別にグローバルに知られる名前として共有されます。ただし、Fortranでは、共通ブロックは共有されるものです。いくつかの例を示しますが、各例ではi
、another_integer
、およびmy_array
が共有され、10x10の実数配列です。
たとえば、Cでは、次のように共有メモリを定義できます:
int i; int another_integer; float my_array[10][10];
および次のようにしてこれらのデータを他の場所で使用できます:
extern float my_array[10][10]; extern int i; extern int another_integer;
ストレージを宣言するモジュールと、そのストレージを使用する別のモジュールがあります。また、定義と使用が同じ順序になっていないことに注意してください。これは、Cでは、ほとんどの言語でi
、another_integer
、およびmy_array
がすべて共有アイテムであるためです。Fortranではそうではありません。Fortranでは、このストレージを共有するすべてのルーチンが次のような定義を持ちます:
common i, another_integer, my_array integer another_integer real my_array(10,10)
この共通ブロックは、リンク可能な名前付き構造としてデータのブロックとして保存されます。ただし、その名前はわかりません。さまざまなコンパイラは、このブロックにさまざまな名前を付けます。一部のシステムでは、ブロックに名前が実際にはありません。この問題を回避するには、構造に名前を付けます。たとえば、次のようにします。
common /my_block/ i, another_integer, my_array integer another_integer real my_array(10,10)
この形式を使用すると、異なる2つのFortranプログラムが同じストレージ領域を識別し、共有し、すべての共有ストレージの構造を知る必要がなくなります。また、この形式を使用すると、Cまたは他のプログラムもストレージを共有できます。たとえば、このストレージを共有したいCプログラムは、次のように同じストレージを宣言します。
extern struct { int i; int another_integer; float my_array[10][10]; } my_block;
上記の例では、my_block
名が一致していることが重要であり、タイプ、サイズ、および順序が一致していることが重要です。ただし、内部的に名前が一致している必要はないため、これらの名前はローカルでのみ知られています。また、上記の例では、Fortranのmy_array(i,j)
がCのmy_block.my_aArray[j][i]
と一致していることに注意してください。
バイトアライメント
[編集]組込みのデータ型のバイトアライメントは、適切な種類を使用することでほとんど保証されます。Fortranには、派生データ型がバイトアライメントされていることを自動的に保証する方法はありません。ただし、プログラマがデータに適切なパディングを挿入することは非常に簡単です。たとえば、文字と整数を含む派生型があるとします
type :: my_type integer (kind=4) :: ival character (len=1) :: letter end type
この型の配列は、要素が5バイトのサイズになります。このタイプの配列の要素を8バイトごとに配置したい場合は、3バイトのパディングを追加する必要があります。これは、他の目的で使用されないキャラクターをパディングとして追加することで行うことができます。
type :: my_type integer (kind=4) :: ival character (len=1) :: letter character (len=3) :: padding end type
ポインタを使用したメモリ管理
[編集]Fortranでは、ポインタを他のデータの エイリアス として使用できます。たとえば、行列の行のようなものです。
ポインタの状態
[編集]各ポインタは、次の状態のいずれかにあります
- 未定義
- 初期化されていない
- 定義済
-
- null/未代入
- どのデータのエイリアスでもありません
- 代入済み
- いくつかのデータのエイリアスです。
組み込み関数associated
は、第2および第3の状態を区別します。
代入
[編集]概要
[編集]次のような例を使用します:ポインタptr
は、実数値x
のエイリアスです。
real, target :: x real, pointer :: ptr ptr => x
次の例では、実数行列matr
を対象とし、ポインタptr
は特定の行のエイリアスとして機能する必要があります。
real, dimension (4, 4), target :: matr real, dimension (:), pointer :: ptr ptr => matr(2, :)
ポインタを他のポインタに指定することもできます。 これにより、それらが最初のポインタと同じデータのエイリアスになります。以下の例を参照してください。
real, target :: x real, pointer :: ptr1, ptr2 ptr1 => x ptr2 => ptr1
通常の代入とポインタ代入
[編集]ポインタの通常の代入とポインタ代入の違いは、次の等式で説明できます。次のセットアップを想定してください
real, target :: x1, x2 real, pointer :: ptr1, ptr2 ptr1 => x1 ptr2 => x2
ポインタの通常の代入は、それらが指すデータの代入につながります。これは、次の2つのステートメントが等しいことからわかります。
! 2つの等しいステートメント ptr1 = ptr2 x1 = x2
対照的に、ポインタ代入は、ポインタのエイリアスを変更し、基礎データに変更を加えません。 等しい例文を参照してください。
! 2つの等しいステートメント ptr1 => ptr2 ptr1 => x2
メモリ割り当て
[編集]ポインタの定義後、allocate
コマンドを使用してメモリを割り当てることができます。ポインタが指すメモリは、deallocate
コマンドで無料になります。次の例を参照してください。
program main implicit none real, allocatable :: ptr allocate (ptr) ptr = 1. print *, ptr deallocate (ptr) end program main
例
[編集]アロケータブル対ポインタ
[編集]次のように、配列の次元数はわかっていますが、サイズは不明ですが、割り当てを使用して宣言できます:
real, dimension (:,:), allocatable :: my_array allocate (my_array(10,10)) deallocate (my_array)
ポインタとしても宣言できます:
real, dimension (:,:), pointer :: some_pointer allocate (some_pointer(10,10)) deallocate (some_pointer)
古代のFORTRAN(77およびそれ以前)のバージョンでは、大きな静的配列があり、必要な部分を使用していました。