愛しの Fortran・3改

Fortran について気の向くままに綴ります

書式付き流れ探査入出力 (その6)

停留入出力

書式付き流れ探査入出力は、従来の(書式付き)順番探査入出力とほぼ上位互換で、 機能が拡張されているということを見てきました。

元々、流れ探査は C のストリーム入出力との互換性を意識した機能です。 ぱっと見て C と Fortran のテキスト入出力の大きな違いは、 C が文字単位のアクセスと、Fortran が行単位のアクセスを主に意図しているところです。 従来との上位互換のため、書式付き流れ探査入出力も、既定では行単位の入出力になっています。

一方で、C により似せるための方策として、停留入出力というのがあります。

      WRITE(UNIT,"(G0)",ADVANCE="NO") "a"
      WRITE(UNIT,"(G0)",ADVANCE="NO") "b"
      WRITE(UNIT,"(G0)",ADVANCE="NO") NEW_LINE("")
      WRITE(UNIT,"(G0)",ADVANCE="NO") "c"
      WRITE(UNIT,"(G0)",ADVANCE="NO") NEW_LINE("")

WRITE 文に「ADVANCE="NO"」指定子を付けます。 こうすると、WRITE 文が既定では暗黙的に行う末尾の改行が省かれます。 これを Fortran 用語では「停留入出力」(nonadvancing input/output) と呼びます。 逆に通常の、文の実行ごとに改行される入出力は「前進入出力」(advancing ー) です。

上のコード片の出力は

ab
c

になります。 上の例では 1 文字ずつ出力しているので、C で言う fputc() の代わりになって、 C 言語用のアルゴリズムを「直訳」して実装することにも便利です。

ただし、入力については C との違いがもう少し大きくなります。 下は「入力のそのまま出力」を C 流に実装するプログラム片です。

      CHARACTER :: CH

      DO
        READ(UNIT,"(A)",ADVANCE="NO",IOSTAT=IOSTAT) CH
        IF (IOSTAT > 0) THEN
          ERROR STOP 74  ! エラー
        ELSE IF (IS_IOSTAT_END(IOSTAT)) THEN
          EXIT  ! EOF
        END IF

        IF (IS_IOSTAT_EOR(IOSTAT)) THEN
          WRITE(*,"()")  ! '\n'
        ELSE
          WRITE(*,"(G0)",ADVANCE="NO") CH
        END IF
      END DO

C だと fgetc() の戻り値が直接 '\n'EOF だったりしますが、 Fortran では行末とファイル末(それにエラー)は IOSTAT 指定子の値に返されます。

(つづく、次回最終回)

書式付き流れ探査入出力 (その5)

POS 指定子

書式付き流れ探査入出力では、一度通ったところに戻れます。 つまり

      INTEGER :: POS

      WRITE(UNIT,"(G0)") "A"
      INQUIRE(UNIT,POS=POS)
      WRITE(UNIT,"(G0)") "B"
      WRITE(UNIT,"(G0)") "C"
      WRITE(UNIT,"(G0)",POS=POS) "D"

この例では A と書いた直後の位置を整数変数 POS にとってあります。その後、B、C と 2 行書きますが、次に POS の位置に D と書きます。すると、先に書いた B、C は削除されて、A の次の行に D とあるファイルになります。

なお、C 言語のテキストストリームでは一度書いたデータを上書きした場合に、その先が削除されるかどうかは処理系によりますが、Fortran の書式付き流れ探査出力では書いたらそこがファイルの終端になって、その先は削除されることになっています。

上の例では何がしたいのか意味不明ですが、書いた場所を覚えておいて、後で戻ってそこから読む、といった用途に使えます。

ファイルの先頭位置は 1 です。基本的には予め INQUIRE 文で POS をとっておいた位置にしか 戻れませんが、1 だけは聞かなくても戻れます。

順番探査ファイルには REWIND 文、BACKSPACE 文というのがありましたが、書式付き流れ探査入出力ではより柔軟に位置付けができます。

(つづく)

書式付き流れ探査入出力 (その4)

改行文字による改行

書式付き流れ探査出力では、「改行文字」(newline character) を出力することにより、 現在記録へのデータ転送を終了して、続く出力が新たな記録を作成するようにできます (つまり改行されます、前回の用語で言えば、現在位置に記録マーカが書かれます)。

      WRITE(UNIT,"(*(G0))") &
       "First line", NEW_LINE(""), "Second line"

これの出力結果は

First line
Second line

です。 組込み関数 NEW_LINE は引数と同じ種別の文字型の「改行文字」を返します。 ちなみに改行文字は ASCII コードの(十進)10 番、 C で言えば「'\xa'」(つまり「'\n'」)と決められています。

普通に順番探査出力でも「'\n'」を書けば改行されるだろ、と思ったアナタ。

      WRITE(*,"(*(G0))") &
       "First line", ACHAR(10), "Second line"

Unix 系では上と同じ出力結果。) 微妙に違うことに注意してください。順番探査出力では、1 つの記録内(同じ行内) の 1 文字として ACHAR(10) が出力されます。 その結果、改行されて記録が 2 つになって見えるとしたら、それは実は規格外の動作です。 「処理系は、書式付き記録において、幾つかの制御文字の使用を禁止してもよい」 という規定によって、処理系は記録内に ACHAR(10) を入れることを禁止しているのを、 規格外の処理系依存の動作をさせているのです。

Windows 系の Fortran 処理系で同じコードを実行させたときに、ACHAR(10) を CR-LF ペアに変換する処理系もあれば、そのまま LF だけにする処理系もあるかもしれません。 そもそも ACHAR(10) を書式付き記録内に書くのは規格外(と処理系が決めた)なので、 どちらの動作をするも規格は感知しない。 その上で利便性から ACHAR(10) が出力された場合の処理系依存動作が実装されてきた、 という経緯があったのです。 これが Fortran 2003 の書式付き流れ探査の導入で、ACHAR(10) の出力は、 その文字そのものを記録内に書くのではなく、そこで改行する意味で使えるようになったのです (ちなみにこれが「ほぼ上位互換」の「ほぼ」の意味です)。

改行文字が NEW_LINE("") という関数になっているのは、 基本文字型でない文字型によるファイル出力時にも同じ仕組みを使うためです。

次回は POS 指定子によるファイル位置の指定です。

(つづく)

書式付き流れ探査入出力 (その3)

基本的な使い方

ようやく本題です。 書式付き流れ探査出力の OPEN 文です。

      OPEN(FILE=FILE,STATUS="REPLACE",ACTION="WRITE", &
       ACCESS="STREAM",FORM="FORMATTED",NEWUNIT=UNIT)

形としては

  • FORM="FORMATTED"」を省略すると書式なし流れ探査出力
  • さらに「ACCESS="STREAM"」を省略すると(書式付き)順番探査出力

になる形式です。 くどいようですが、いつもの順番探査出力の OPEN 文に、 流れ探査を指定する ACCESS 指定子、 そして(ACCESS="STREAM" は既定で書式なしなので) 書式付きを指定する FORM 指定子を指定すると、 書式付き流れ探査出力になります。

書式探査出力は、ほぼ順番探査出力と同様に使えます。

      WRITE(UNIT,"(*(G0,1X))") "Hello, World!"

この例は書式付き流れ探査出力ですが、順番探査出力とまったく同じ文です。

「書式付き流れ探査出力は順番探査出力の上位互換になっている」 というのは、以下の 2 つの機能が追加されている点です。

  1. 改行文字の出力による改行。
  2. POS 指定子によるファイル位置の指定。

これらを次回以降見ていきます。

(つづく)

書式付き流れ探査入出力 (その2)

用語の確認

Fortran 用語は独特なので、まずは用語を整理しておきましょう(まぁどのプログラミング言語の用語も独自のものですが、Fortran はかなり少数派なので)。

「プログラムの外部の媒体中に存在するファイル」を「外部ファイル (external file)」と呼びます。(Fortranでは「内部ファイル」という語を「入出力文の対象とする文字変数」という意味で使います。)

外部ファイルは一連の「ファイル記憶単位 (file storage unit) 」で構成されます。C 言語で言う「バイト」のことですね。事実上 8 ビットですが、規格上は処理系依存です。

外部ファイルを、データの列という以上の構造はないと扱う場合、(書式なし)流れファイル ((unformatted) stream file) と呼びます。C で言うバイナリストリームに対応する概念です。

一方で、記録 (record) の列でもあるファイルを書式付き流れファイル (formatted stream file) と呼びます(この語は規格中に現れますが、明確には定義されていないので意図せず使われたのかもしれません。でも便利なのでここでは使います)。C で言うテキストストリームに対応する概念です。Fortranで言う「記録」は実質的にはテキストファイルの行のことです。

記録は文字列とそれに続く記録マーカ (record marker)。ただし、ファイルの最後の記録は記録マーカを持たないことがあり、この場合最後の記録は不完全 (incomplete) と言う。このあたりも「記録マーカ」を「改行文字」と読み替えると C のテキストストリームの定義をなぞっています。そもそも Fortran の流れ (stream) 入出力は C が扱うファイルとの互換性を意図しているわけですから。

1 つの記録マーカのファイル記憶単位数は処理系依存です。事実上は 1 文字(UNIXApple 系)と 2 文字(MS 系)が流通しています。

書式付き流れ探査入出力 (その1b)

時期は熟した? 余談

「説明していきたいと思います」と前回結んでおきながら、背景の話とかが好きなのでいきなり余談になります。

Fortran 2008 までは「書式付き流れ探査として接続されている場合」「ファイル中のファイル記憶単位を読んだり書いたりすることは、書式付き流れ探査入出力文によってだけ行える」という規定がありました (Fortran 2008 では 9.3.3.4、2003 では 9.2.2.3) が、これが Fortran 2018 では削除されています (12.3.3.4)。前書きの Fortran 2018 での変更点でも言及はありません。

この規定を削除したとしても、他の規定から、書式付き流れ探査として接続した装置で書式付き流れ探査入出力以外の入出力が規格外になるのは明らかです。

これは Fortran 2018 で同じファイルを同時に複数の装置で読み書きすることが許容された(ただし結果は処理系依存)からの遠回りな措置なのか(つまり、一方の装置で書式付き流れ探査に接続し、他方の装置に同時に書式付き流れ探査で接続して入出力することを禁止しないため)、他の規定と重複するので不要と判断されたのか、意図せず削除してしまったのかのかは判りかねます。

順番探査での接続についての「このファイルの記録を順番探査入出力文以外によって読んだり書いたりしてはならない」はそのまま残されています。

(つづく)

書式付き流れ探査入出力 (その1)

時期は熟した?

2021 年現在、Fortran 2003 で導入された書式付き流れ探査入出力 (formatted stream output) はあまり使われていません。 従来使われている書式付き順番探査入出力と、ほぼ上位互換で機能が拡充されているので、 使わない手はありません。

なお、書式なし流れ探査入出力 (unformatted ー) はすでに各方面で便利に使われているところです。

以下、説明の都合上

  • 書式付き流れ探査出力で作ったファイルを書式付き流れファイル、
  • 書式付き順番探査出力で作ったファイルを書式付き順番ファイル

と呼びます。

主な不都合は次の 2 つ。

  1. 書式付き順番探査入出力による書式付き流れファイルの読み書き、 書式付き流れ探査入出力による書式付き順番探査ファイルの読み書きは保証されていない。
  2. 入出力文既定の「*」装置は書式付き順番探査入出力。これを書式付き流れ探査入出力にはできない。

特に 1. は深刻です。 規格では書式付き順番探査で読み書きできるファイルを 書式付き流れ探査入出力で読み書きできるかは処理系依存としています。 しかし、現実には 2021 年現在では実用されるすべての Fortran 処理系で書式付き流れファイルと 書式付き順番探査ファイルとは同じものです。 両方の実装が必須である以上、今後、新規の処理系で両者の互換性を欠く実装は考えにくい。 なので 1. の問題は事実上解消されたと考えます。

そこで利用普及をはかって書式付き流れ探査入出力について詳細を説明していきたいと思います。

(つづく)