len_trim
を組み込み関数扱いにするための改造2022/07/02
FORTRAN77 を C/C++ に変換する f2c( Netlib )は、GCC(g77, gortran)と異なり len_trim
が組み込み扱いになっていなくて、文字列を読み書きするときや C/C++ との混合言語プログラミングが必要な場合には非常に不便です。
代替方法として len_trim
関数の代替品 をすでに発表していますが、プログラミング安全性向上のための implicit none
宣言下ではいちいち external
宣言と型宣言を追記しないと使えず、何かと面倒です。
このため根本解決として、len_trim
自体を len
関数と同様の組み込み扱いにしてしまおう、という改造方法です。
diff -u -w intr.org.c intr.c --- intr.org.c Tue Aug 21 13:01:42 2001 +++ intr.c Sat Jul 02 13:59:19 2022 @@ -44,7 +44,7 @@ LOCAL struct Intrblock { - char intrfname[8]; + char intrfname[12]; struct Intrbits intrval; } intrtab[ ] = { @@ -223,6 +223,7 @@ "ishft", { INTRBGEN, TYUNKNOWN, OPBITSH, 0, 0, 2 }, "ishftc", { INTRBGEN, TYUNKNOWN, OPBITSHC, 0, 0, 2 }, +"len_trim", { INTRSPEC, TYLONG, 95 }, "" }; @@ -232,7 +233,7 @@ this type */ char rtype; /* Result type */ char nargs; /* Number of arguments */ - char spxname[8]; /* Name of the function in Fortran */ + char spxname[12]; /* Name of the function in Fortran */ char othername; /* index into callbyvalue table */ } spectab[ ] = { @@ -366,6 +367,9 @@ { TYREAL,TYREAL,2,"r_scal" }, { TYDREAL,TYDREAL,2,"d_scal" }, + + { TYCHAR,TYSHORT,1,"h_len_trim" }, + { TYCHAR,TYLONG,1,"i_len_trim" }, { 0 } } ;
spxname
のサイズを 8 から 12 に拡大しているのは、 i_len_trim
という文字列が 10 文字であり、もとの 8 だと入りきらないからである。この先頭の i_
が何であるかは、あとのほうで解説する。
intrfname
も同様である。
それぞれ 11 と 9 でも足りるのだが、アラインメントを考えれば 12 にするのが合理的か。
次に、
LOCAL struct Intrblock { char intrfname[12]; struct Intrbits intrval; } intrtab[ ] = { (FORTRAN77 組み込み関数名称とその種別分類・戻り値型・引数型その他情報) }と
LOCAL struct Specblock { char atype; /* Argument type; every arg must have this type */ char rtype; /* Result type */ char nargs; /* Number of arguments */ char spxname[12]; /* Name of the function in Fortran */ char othername; /* index into callbyvalue table */ } spectab[ ] = { (FORTRAN77 組み込み関数に対応する libf2c 関数の呼び出し方リスト) }がペアになることで、
intrtab
に示された FORTRAN77 の戻り値型暗黙と、spectab
に示された C の戻り値型明示が、翻訳されている。intrtab
と spectab
はおそらく intrinsic table、spec table の略なのだろう。
ここで FORTRAN77 の組み込み関数である len
に着目すると、intr.c 122 行目 intrtab
の初期化リスト中にある
"len", { INTRSPEC, TYLONG, 27 },
という行は、spectab
の 27 番目から当該翻訳単位が入っているという意味のようであり、spectab
の初期化リストをたどっていくと実際にそうなっている。intrtab
の側には TYLONG
しか記述がないが、intr.c 275 行目 spectab
の側では次のように面白いことになっている。
{ TYCHAR,TYSHORT,1,"h_len" },
{ TYCHAR,TYLONG,1,"i_len" },
spectab
の 27 番目は TYSHORT
、28 番目は TYLONG
を返すと定義されていて、システムに合った戻り値の型を検索してそれに合った翻訳を行うために、intrtab
側において見つかった番号に対し、その番号そのままの翻訳か、それとも戻り値型に合わせてひとつ次の番号のものを選ぶかを変えている。intr.c 989 行目付近 intraddr(Namep np)
内の case INTRSPEC:
から始まるところにそれらしき選択をしている箇所がある。intrtab
側において 28 は飛び番とされ、len
の次にある index
には 29 という番号が与えられているのだ(まあ、CPU もメモリーも乏しかった古(いにしえ)の流儀としか言いようがないが、これはこれでまあそういうものだと思うことにする)。
これらからとにかくわかることは、ソースコードの可読性を気にして既存の組み込み関数定義の途中に今回の len_trim
を挟み込むのはけっこう面倒であり、末尾に追加するのが簡単、ということである。
intrtab
の初期化リストをたどっていくと、既存の組み込み関数定義のうち、種別 INTRSPEC
が指定されている最後のものは scal
であり 93 番を与えている(実際には fpscal
と書かれているが頭の fp
はおそらく翻訳における型候補を示すものだろう)。一方、spectab
側では 93 番目に r_scal
が、94 番目に d_scal
が存在している(これらは real
と doublereal
の使い分けである)。ということは、最初の空き番号が 95 番ということになる。
ということで結論は、len
の場合に倣って、intrtab
側では len_trim
に 95 番を与え、spectab
側では 95 番目を戻り値 short int
版 h_len_trim
、96 番目を戻り値 long int
版 i_len_trim
として定義すればよいことになる。なお、これらの番号自体は f2c の実行ファイルで閉じたものとなり、libf2c 側への波及はない。
本当は TYLONG
ではなく TYFTNLEN
を返したいところであるが、オリジナルの len
も TYLONG
なので、目くじらは立てないことにする。f2c 実行時に -i2
オプションを与えない限りにおいて ftnlen
も integer
も long int
になるので一応問題はない。C言語から見た理想は size_t
なのだがこれは FORTRAN77 では逆立ちしても扱えない型なので、妥協するしかない。
以上でソースコードの変更に関する解説は終わりだが、これを行うと intr.c のチェックサムが変わってしまうので、そのままでは make が失敗する。ただし、そのときにチェックサムの比較が表示されるので、xsum0.out に書かれている比較用チェックサムとバイト数をその値に書き換え、make を実行しなおせば、ビルドが通るようになる。( nmake -f makefile.vc
なお makefile.vc 自体も CFLAGS = -Ot1 -nologo -DNO_LONG_LONG
を CFLAGS = -O2 -nologo
に)
これで implicit none
宣言をしてあっても len_trim
をしかるべき型の組み込み関数として使用することが可能となる。
FORTRAN77 |
改造後の f2c で翻訳した C (f2c が出す定型冒頭コメントは省略) |
implicit none integer i i = len_trim("abcde") end |
#include "f2c.h" /* Main program */ int MAIN__(void) { /* Builtin functions */ integer i_len_trim(char *, ftnlen); /* Local variables */ static integer i__; i__ = i_len_trim("abcde", (ftnlen)5); return 0; } /* MAIN__ */ |
あとは、short int
版 h_len_trim
と long int
版 i_len_trim
を用意すればよい。
#include <stdlib.h> #include <assert.h> #include <limits.h> #include "f2c.h" integer i_len_trim(const char *s, ftnlen slen) { const char *wp; if(slen < 0) slen=0; for( wp = s + (size_t)slen -1; wp >= s && (*wp == '\x20' || *wp == '\0'); --wp ){ ; } if(wp >= s){ return (integer)(wp - s + 1); } return 0; } shortint h_len_trim(const char *s, ftnlen slen) { assert(slen < SHRT_MAX); if(slen > SHRT_MAX - 1){ abort(); } return (shortint)i_len_trim(s, slen); } |
KR_headers
と __cplusplus
への対応を加えたものを i_len_trim.c として置いておきます。
long int
版は len_trim
関数の代替品 の len_trim.c とほとんど同じ。相違点は、戻り値の型が違うこと、関数名が違うこと(戻り値型の接頭辞)、関数名末尾のアンダースコアが無いこと、である。
short int
版も用意しているが、出番は事実上ないだろう。f2c 実行時に -i2
オプションを与える必要性は、余程のことがない限り 2020 年代には考えられないからだ。
トップページ /
計算機関係の雑多なこと /
f2c において len_trim
を組み込み関数扱いにするための改造
このサイトについて・著者連絡先