f2c の tips - DO ループの CYCLE文・EXIT文に対応させるプリプロセッサと len_trim 関数の代替品

2018/04/15

FORTRAN77 を C/C++ に変換する(ただし人間には unreadable な出力しか出せない) f2c は、構造化 do ループ記述に必要な cycle(C の continue 相当)exit(文脈依存で C の break 相当、プログラムを終了させる exit() 関数とは異なる)に対応していません。これらは本来 FORTRAN77 規格外の拡張構文なので、対応していないこと自体はやむを得ませんが、多くの FORTRAN77 コンパイラでデファクトスタンダード拡張構文としてサポートされています。これらが f2c でも使えるようになれば、古いプログラムのメンテナンス(再コンパイルだけでなく、修正やリファクタリング)をするうえで役に立ちます。

また、FORTRAN77 で書かれた古いプログラムを(特に64bitで)コンパイルするにあたり、GNU GPL v.3 の感染回避策手段としても f2c を使うことができます。

最新の gfortran は実行時に不可分なランタイムライブラリDLLが GNU GPL v.3 である。GPL v.2 まで存在していたランタイムライブラリ例外は GPL v.3 にはないと解釈する方が法的に安全側のため、このような課題が生じてしまう。また 64bit の FORTRAN コンパイラは現時点で MinGW-w64 project の gcc に含まれる gfortran くらいしか選択肢がない(Intel Fortran は知らん)。一方、32bit でよいなら GCC-3.4.5 がまだあることはあるが、近年では周囲環境の変化でインストールに手古摺るようになり、古いランタイムのサポートや周辺ツールの相性にも不安がある。

そこで、cycle / exitgotocontinue に置き換えて f2c の対応枠内に収めるような、最低限のプリプロセッサを作ってみました。

実用プログラムを記述する際に便利というより必須な関数として len_trim が挙げられます。これもまた FORTRAN77 規格外の関数で、g77/gfortran には組み込まれているが f2c ではサポートされていません。f2c で変換したときには代替品が必要になります。C言語で作成したので、併せて掲載します。

len_trim に関する別解は こちら

f2c およびそれに必要な libf2c は Netlib にあります。未だに、時々メンテナンスされているようです。

目次


プリプロセッサ

詳細な解説は割愛します。perl スクリプトをご覧ください。改良すべき点は山ほどあります。気が向いたら改良するかもしれませんし、放置するかもしれません。

需要や使い方は人それぞれである上に、至極あたりまえのことを愚直にやっているだけですので、何も主張しません。ご自由にお使いください。(zlibライセンスから3つの要求箇条を取り除いたものと考えてください…って、なんじゃそりゃ。)

ダウンロード:for2c.pl [2018/04/15]

以下の事を行います。

  1. cycle 並びに exitgotocontinue の組み合わせに置き換える。その際、ソースコード内に既にある文番号は避け、自動で文番号を付与する。
  2. 無限ループを記述する際に、文番号無しでかつループ制御構文も無しに
    do
      foobar(baz)
    enddo
    と記述してある場合(FORTRAN77 規格外、g77 など多くのコンパイラで許容されているが f2c は文法違反と判断してしまう)、これを f2c が対応している記述である(これもFORTRAN77 規格外だが)
    do while(.true.)
      foobar(baz)
    enddo
    に置き換える。
  3. 上記以外の do ループはそのまま放置。
  4. 結果を中間ファイルに書き出す。
  5. f2c を起動して中間ファイルを C に変換する。f2c にはインクルードパスを制御する環境変数は無いようであるため、元のソースコードをパス付で示された場合は、いったんそのパスに移動して f2c を起動し、戻るようにしている。
  6. 中間ファイルを消す。

len_trim 関数の代替品

ダウンロード:len_trim.c

これも、ご自由にお使いください。

なお別解として、f2c において len_trim 自体を組み込み関数としてしまう方法は こちら をご覧ください。


libf2c の MS-VC++ によるビルド

libf2c は32bit/64bit各々同じ手順でコンパイル可能です。f2c の変換出力に対して64bitのCコンパイラを使えば、FORTRAN77のプログラムの64bit化も条件次第で可能です。

libf2c の makefile.vc の修正

修正箇所説明
9
修正前
CFLAGS = -DUSE_CLOCK -DMSDOS -DNO_ONEXIT -Ot1 -DNO_My_ctype -DNO_ISATTY
※長いので改行可指定を使っていますが実際は1行です。

-MD でランタイムをダイナミックリンクにする(MSVCR*.DLL を使う)。
デバッグビルドの場合は -MD-MDd に変えればよい。
-Ot1-Ot-O1(機械語サイズ最小・低速)を上書きと解釈されて意図と真逆の指定になるので、-O2(最高速度)に修正する(ただし、大した差は出ない)。
ssize_t という unix 由来の typedef (→通信用語の基礎知識)は MS-VC++ には無いので、マクロ NO_SSIZE_T を定義してそれを伝える。

-DWANT_LEAD_0-nologo はお好みで構いません。前者はwrite文で浮動小数点数を出力するときに先行ゼロを省略しない(C/C++と親和性が高い表記)、後者はMS-VC++の著作権表示の抑止(コンソールの表示がすっきり)。
修正後
CFLAGS = -MD -DUSE_CLOCK -DMSDOS -DNO_ONEXIT -O2 -DNO_My_ctype -DNO_ISATTY -DNO_SSIZE_T -DWANT_LEAD_0 -nologo
192
修正前
	comptry.bat $(CC) $(CFLAGS) -DNO_FPINIT arithchk.c
※長いので改行可指定を使っていますが実際は1行です。

バッチファイル comptry.bat は、 long long 型がまだ無い頃の古い MS-VC++ においてコンパイルエラーが生じた時には -DNO_LONG_LONG を加えて arithchk.c のコンパイルを再試行するためのもの。MS-VC++2005 以降では long long 型が定義済みなのでこのハックは不要であり、直接実行に修正する。
修正後
	$(CC) $(CFLAGS) -DNO_FPINIT arithchk.c
183-184
(+185)
修正前
vcf2c.lib: $w
	lib -out:vcf2c.lib @libf2c.lbc
この追記は 32bit と 64bit を作り分けるときに便利ですが必須ではありません。詳細は下の方の「ビルド」を参照。
-out: では成果物のファイル名を指定します。私は以下のようにしています。
  • 32bit ビルド用に -out:vcf2c.lib の代わりに -out:vcf2c32.lib とした Makefile.vc.32 を作成。
  • 64bit ビルド用に -out:vcf2c.lib の代わりに -out:vcf2c64.lib とした Makefile.vc.64 を作成。
32bit と 64bit を作り分けるにあたり、ビルド成功した時点で arith.h とオブジェクトファイルを消してしまいます。理由は、arith.h は FORTRAN の変数型が C のどの変数型に対応するかを規定する動的自動生成のヘッダファイルであり、32bit ビルドと 64bit ビルドで中身が異なるためです。
修正後
vcf2c.lib: $w
	lib -out:vcf2c64.lib @libf2c.lbc
	del arith.h *.obj

ssize_t について:signed size_t という意味ととれば良いでしょう。FORTRAN および Fortran には符号なし整数の概念が原則存在しない(処理系依存)という事情もあります。C言語との混合言語プログラミングにおけるインターフェイスでは文字列長の受け渡しなどにおいて size_t (符号なし)との変換が求められることがあり、下手をするとバッファオーバーランになるため特に注意を要します。

libf2c のソースコードの修正(致命的バグ:READ 文で US-ASCII 以外(文字コード128以上)の文字を扱うとき)

詳細は libf2c のバグ を参照してください。
ファイルやコンソールから READ 文で読み込み時に US-ASCII 以外(文字コード128以上)の文字を扱う可能性がある場合は、読み込み文字種判定に関する未定義動作と配列添字オーバーランのバグを修正しておくことを推奨します。

libf2c のソースコードの修正(MS-VC++コンパイラとの相性問題改善)

以下の2か所

extern int ungetc(int, FILE*); /* for systems with a buggy stdio.h */

というのがある。これはMS-VC++では(特に /MD または /MDd の場合)かえって正しいプロトタイプ宣言を上書きしてしまい「warning C4273: 'ungetc' : dll リンクが一貫していません。」が発生する。MS-VC++は昔から正しい宣言がされているので、以下のようにする。

#ifndef _MSC_VER // to avoid warning C4273 with compiler option /MD or /MDd
 extern int ungetc(int, FILE*);	/* for systems with a buggy stdio.h */
#endif

そのほか致命的ではないものの、以下の問題もあります。どうするかは、各自で判断するしかありません。

ビルド

再ビルドするときはその前に del arith.h *.obj を実行すること。arith.h は動的自動生成のヘッダファイルであり一時的に存在する arithchk.exe によって作られ、いくつかのソースファイルがこれを #include している。このため、再ビルド前後で 32bit と 64bit が混在するとおかしなことになる。

あとは、nmake -f makefile.vc を実行で行けます。成果物のファイル名は makefile.vc のオリジナルのままでは vcf2c.lib となります。このファイル名は makefile.vc の183-184行目で指定されており、32bit と 64bit、Release と Debug など、必要に応じて成果物ファイル名を変更した makefile を用意しておくと便利です。


f2cでCに変換したソースを MS-VC++ でコマンドラインビルドしようとして「LINK : fatal error LNK1561: エントリー ポイントを定義しなければなりません。」と言われる場合

次のようにリンカーオプションでコンソールプログラムであることを指定してください。vcf2c.lib の中にある main.obj が使われるようになります。

cl /MD foobar.c vcf2c.lib /link /SUBSYSTEM:CONSOLE
Windows XP でも実行可能にする場合(実機がないため検証してません):
cl /MD foobar.c vcf2c.lib /link /SUBSYSTEM:CONSOLE,5.01
メインルーチンを含むソースコードをf2cで変換した出力には明示的な main() がなく、代わりにFORTRANメインルーチンを表す int MAIN__(void) があります。
vcf2c.lib の中に main() があってそれがコマンドライン引数や atexit() などを処置しつつ MAIN__() を呼び出すのですが、MS-VC++ ではコンパイラに与えるソースコードに main()/wmain()、WinMain()/wWinMain() がいずれも無い場合はエントリーポイント種別が自動では設定されません。このため、使用するサブシステムがコンソールである(ウィンドウプログラムやDLLではない)ことを明示的に示す必要があります。
→MSDN リンカーオプション /SUBSYSTEM (サブシステムの指定) (2013) (2019) 並びに /ENTRY (エントリ ポイント シンボル) (2013はリンク切れ) (2019)

unix系OS由来の Mingw-gcc (-mwindows や -shared を付けなければ -mconsole が自動で仮定される)と使い勝手が異なるところです。


略歴


トップページ / 計算機関係の雑多なこと / f2c の tips - DO ループの CYCLE文・EXIT文に対応させるプリプロセッサとlen_trim関数の代替品
このサイトについて・著者連絡先